mirror of
https://github.com/bitwarden/mobile
synced 2025-12-10 05:13:31 +00:00
Compare commits
2 Commits
tech-debt/
...
v1.11.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8e1bafe1f | ||
|
|
14ec79f6cc |
112
.editorconfig
112
.editorconfig
@@ -1,112 +0,0 @@
|
|||||||
# EditorConfig is awesome: http://EditorConfig.org
|
|
||||||
|
|
||||||
# top-most EditorConfig file
|
|
||||||
root = true
|
|
||||||
|
|
||||||
# Don't use tabs for indentation.
|
|
||||||
[*]
|
|
||||||
indent_style = space
|
|
||||||
# (Please don't specify an indent_size here; that has too many unintended consequences.)
|
|
||||||
|
|
||||||
# Code files
|
|
||||||
[*.{cs,csx,vb,vbx}]
|
|
||||||
indent_size = 4
|
|
||||||
insert_final_newline = true
|
|
||||||
charset = utf-8-bom
|
|
||||||
|
|
||||||
# Xml project files
|
|
||||||
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
# Xml config files
|
|
||||||
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
# JSON files
|
|
||||||
[*.json]
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
# Dotnet code style settings:
|
|
||||||
[*.{cs,vb}]
|
|
||||||
# Sort using and Import directives with System.* appearing first
|
|
||||||
dotnet_sort_system_directives_first = true
|
|
||||||
# Avoid "this." and "Me." if not necessary
|
|
||||||
dotnet_style_qualification_for_field = false:suggestion
|
|
||||||
dotnet_style_qualification_for_property = false:suggestion
|
|
||||||
dotnet_style_qualification_for_method = false:suggestion
|
|
||||||
dotnet_style_qualification_for_event = false:suggestion
|
|
||||||
|
|
||||||
# Use language keywords instead of framework type names for type references
|
|
||||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
|
||||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
|
||||||
|
|
||||||
# Suggest more modern language features when available
|
|
||||||
dotnet_style_object_initializer = true:suggestion
|
|
||||||
dotnet_style_collection_initializer = true:suggestion
|
|
||||||
dotnet_style_coalesce_expression = true:suggestion
|
|
||||||
dotnet_style_null_propagation = true:suggestion
|
|
||||||
dotnet_style_explicit_tuple_names = true:suggestion
|
|
||||||
|
|
||||||
# Prefix private members with underscore
|
|
||||||
dotnet_naming_rule.private_members_with_underscore.symbols = private_members
|
|
||||||
dotnet_naming_rule.private_members_with_underscore.style = underscore_prefix
|
|
||||||
dotnet_naming_rule.private_members_with_underscore.severity = suggestion
|
|
||||||
|
|
||||||
dotnet_naming_symbols.private_members.applicable_kinds = field
|
|
||||||
dotnet_naming_symbols.private_members.applicable_accessibilities = private
|
|
||||||
dotnet_naming_symbols.private_members.required_modifiers = readonly
|
|
||||||
|
|
||||||
dotnet_naming_style.underscore_prefix.capitalization = camel_case
|
|
||||||
dotnet_naming_style.underscore_prefix.required_prefix = _
|
|
||||||
dotnet_naming_style.underscore_prefix.required_suffix =
|
|
||||||
dotnet_naming_style.underscore_prefix.word_separator =
|
|
||||||
|
|
||||||
# Async methods should have "Async" suffix
|
|
||||||
dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods
|
|
||||||
dotnet_naming_rule.async_methods_end_in_async.style = end_in_async
|
|
||||||
dotnet_naming_rule.async_methods_end_in_async.severity = suggestion
|
|
||||||
|
|
||||||
dotnet_naming_symbols.any_async_methods.applicable_kinds = method
|
|
||||||
dotnet_naming_symbols.any_async_methods.applicable_accessibilities = *
|
|
||||||
dotnet_naming_symbols.any_async_methods.required_modifiers = async
|
|
||||||
|
|
||||||
dotnet_naming_style.end_in_async.required_prefix =
|
|
||||||
dotnet_naming_style.end_in_async.required_suffix = Async
|
|
||||||
dotnet_naming_style.end_in_async.capitalization = pascal_case
|
|
||||||
dotnet_naming_style.end_in_async.word_separator =
|
|
||||||
|
|
||||||
# CSharp code style settings:
|
|
||||||
[*.cs]
|
|
||||||
# Prefer "var" everywhere
|
|
||||||
csharp_style_var_for_built_in_types = true:suggestion
|
|
||||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
|
||||||
csharp_style_var_elsewhere = true:suggestion
|
|
||||||
|
|
||||||
# Prefer method-like constructs to have a expression-body
|
|
||||||
csharp_style_expression_bodied_methods = true:none
|
|
||||||
csharp_style_expression_bodied_constructors = true:none
|
|
||||||
csharp_style_expression_bodied_operators = true:none
|
|
||||||
|
|
||||||
# Prefer property-like constructs to have an expression-body
|
|
||||||
csharp_style_expression_bodied_properties = true:none
|
|
||||||
csharp_style_expression_bodied_indexers = true:none
|
|
||||||
csharp_style_expression_bodied_accessors = true:none
|
|
||||||
|
|
||||||
# Suggest more modern language features when available
|
|
||||||
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
|
|
||||||
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
|
|
||||||
csharp_style_inlined_variable_declaration = true:suggestion
|
|
||||||
csharp_style_throw_expression = true:suggestion
|
|
||||||
csharp_style_conditional_delegate_call = true:suggestion
|
|
||||||
|
|
||||||
# Newline settings
|
|
||||||
csharp_new_line_before_open_brace = all
|
|
||||||
csharp_new_line_before_else = true
|
|
||||||
csharp_new_line_before_catch = true
|
|
||||||
csharp_new_line_before_finally = true
|
|
||||||
csharp_new_line_before_members_in_object_initializers = true
|
|
||||||
csharp_new_line_before_members_in_anonymous_types = true
|
|
||||||
|
|
||||||
# All files
|
|
||||||
[*]
|
|
||||||
guidelines = 120
|
|
||||||
63
.gitattributes
vendored
63
.gitattributes
vendored
@@ -1,63 +0,0 @@
|
|||||||
###############################################################################
|
|
||||||
# Set default behavior to automatically normalize line endings.
|
|
||||||
###############################################################################
|
|
||||||
* text=auto
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Set default behavior for command prompt diff.
|
|
||||||
#
|
|
||||||
# This is need for earlier builds of msysgit that does not have it on by
|
|
||||||
# default for csharp files.
|
|
||||||
# Note: This is only used by command line
|
|
||||||
###############################################################################
|
|
||||||
#*.cs diff=csharp
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# Set the merge driver for project and solution files
|
|
||||||
#
|
|
||||||
# Merging from the command prompt will add diff markers to the files if there
|
|
||||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
|
||||||
# the diff markers are never inserted). Diff markers may cause the following
|
|
||||||
# file extensions to fail to load in VS. An alternative would be to treat
|
|
||||||
# these files as binary and thus will always conflict and require user
|
|
||||||
# intervention with every merge. To do so, just uncomment the entries below
|
|
||||||
###############################################################################
|
|
||||||
#*.sln merge=binary
|
|
||||||
#*.csproj merge=binary
|
|
||||||
#*.vbproj merge=binary
|
|
||||||
#*.vcxproj merge=binary
|
|
||||||
#*.vcproj merge=binary
|
|
||||||
#*.dbproj merge=binary
|
|
||||||
#*.fsproj merge=binary
|
|
||||||
#*.lsproj merge=binary
|
|
||||||
#*.wixproj merge=binary
|
|
||||||
#*.modelproj merge=binary
|
|
||||||
#*.sqlproj merge=binary
|
|
||||||
#*.wwaproj merge=binary
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# behavior for image files
|
|
||||||
#
|
|
||||||
# image files are treated as binary by default.
|
|
||||||
###############################################################################
|
|
||||||
#*.jpg binary
|
|
||||||
#*.png binary
|
|
||||||
#*.gif binary
|
|
||||||
|
|
||||||
###############################################################################
|
|
||||||
# diff behavior for common document formats
|
|
||||||
#
|
|
||||||
# Convert binary document formats to text before diffing them. This feature
|
|
||||||
# is only available from the command line. Turn it on by uncommenting the
|
|
||||||
# entries below.
|
|
||||||
###############################################################################
|
|
||||||
#*.doc diff=astextplain
|
|
||||||
#*.DOC diff=astextplain
|
|
||||||
#*.docx diff=astextplain
|
|
||||||
#*.DOCX diff=astextplain
|
|
||||||
#*.dot diff=astextplain
|
|
||||||
#*.DOT diff=astextplain
|
|
||||||
#*.pdf diff=astextplain
|
|
||||||
#*.PDF diff=astextplain
|
|
||||||
#*.rtf diff=astextplain
|
|
||||||
#*.RTF diff=astextplain
|
|
||||||
81
.github/ISSUE_TEMPLATE/bug.yml
vendored
81
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -1,81 +0,0 @@
|
|||||||
name: Bug Report
|
|
||||||
description: File a bug report
|
|
||||||
labels: [bug]
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Thanks for taking the time to fill out this bug report!
|
|
||||||
|
|
||||||
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
|
|
||||||
- type: textarea
|
|
||||||
id: reproduce
|
|
||||||
attributes:
|
|
||||||
label: Steps To Reproduce
|
|
||||||
description: How can we reproduce the behavior.
|
|
||||||
value: |
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. Click on '...'
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: expected
|
|
||||||
attributes:
|
|
||||||
label: Expected Result
|
|
||||||
description: A clear and concise description of what you expected to happen.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: actual
|
|
||||||
attributes:
|
|
||||||
label: Actual Result
|
|
||||||
description: A clear and concise description of what is happening.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
id: screenshots
|
|
||||||
attributes:
|
|
||||||
label: Screenshots or Videos
|
|
||||||
description: If applicable, add screenshots and/or a short video to help explain your problem.
|
|
||||||
- type: textarea
|
|
||||||
id: additional-context
|
|
||||||
attributes:
|
|
||||||
label: Additional Context
|
|
||||||
description: Add any other context about the problem here.
|
|
||||||
- type: dropdown
|
|
||||||
id: os
|
|
||||||
attributes:
|
|
||||||
label: Operating System
|
|
||||||
description: What operating system are you seeing the problem on?
|
|
||||||
multiple: true
|
|
||||||
options:
|
|
||||||
- Android
|
|
||||||
- iOS
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: os-version
|
|
||||||
attributes:
|
|
||||||
label: Operating System Version
|
|
||||||
description: What version of the operating system(s) are you seeing the problem on?
|
|
||||||
- type: input
|
|
||||||
id: device
|
|
||||||
attributes:
|
|
||||||
label: Device
|
|
||||||
description: Which device are you seeing the problem on?
|
|
||||||
placeholder: iPhone 12, Samsung Galaxy S10
|
|
||||||
- type: input
|
|
||||||
id: version
|
|
||||||
attributes:
|
|
||||||
label: Build Version
|
|
||||||
description: What version of our software are you running? (go to "Settings" → "About" in the app)
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: checkboxes
|
|
||||||
id: beta
|
|
||||||
attributes:
|
|
||||||
label: Beta
|
|
||||||
options:
|
|
||||||
- label: Using a pre-release version of the application.
|
|
||||||
17
.github/ISSUE_TEMPLATE/config.yml
vendored
17
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,17 +0,0 @@
|
|||||||
blank_issues_enabled: false
|
|
||||||
contact_links:
|
|
||||||
- name: Report mobile autofill failure
|
|
||||||
url: https://docs.google.com/forms/d/e/1FAIpQLScMopHyN7KGJs8hW562VTzbIGL4KcFnx0wJcsW0GYE1BnPiGA/viewform
|
|
||||||
about: We are aware of some situations where the Bitwarden mobile app will not autofill information correctly. This is something the Bitwarden team is actively working on but need your help as a community and active Bitwarden users!
|
|
||||||
- name: Feature Requests
|
|
||||||
url: https://community.bitwarden.com/c/feature-requests/
|
|
||||||
about: Request new features using the Community Forums. Please search existing feature requests before making a new one.
|
|
||||||
- name: Bitwarden Community Forums
|
|
||||||
url: https://community.bitwarden.com
|
|
||||||
about: Please visit the community forums for general community discussion, support and the development roadmap.
|
|
||||||
- name: Customer Support
|
|
||||||
url: https://bitwarden.com/contact/
|
|
||||||
about: Please contact our customer support for account issues and general customer support.
|
|
||||||
- name: Security Issues
|
|
||||||
url: https://hackerone.com/bitwarden
|
|
||||||
about: We use HackerOne to manage security disclosures.
|
|
||||||
32
.github/PULL_REQUEST_TEMPLATE.md
vendored
32
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,32 +0,0 @@
|
|||||||
## Type of change
|
|
||||||
- [ ] Bug fix
|
|
||||||
- [ ] New feature development
|
|
||||||
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
|
||||||
- [ ] Build/deploy pipeline (DevOps)
|
|
||||||
- [ ] Other
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
<!--Describe what the purpose of this PR is. For example: what bug you're fixing or what new feature you're adding-->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Code changes
|
|
||||||
<!--Explain the changes you've made to each file or major component. This should help the reviewer understand your changes-->
|
|
||||||
<!--Also refer to any related changes or PRs in other repositories-->
|
|
||||||
|
|
||||||
* **file.ext:** Description of what was changed and why
|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
<!--Required for any UI changes. Delete if not applicable-->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Testing requirements
|
|
||||||
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Before you submit
|
|
||||||
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
|
|
||||||
- [ ] This change requires a **documentation update** (notify the documentation team)
|
|
||||||
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
|
||||||
17
.github/resources/export-options-ad-hoc.plist
vendored
17
.github/resources/export-options-ad-hoc.plist
vendored
@@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>method</key>
|
|
||||||
<string>ad-hoc</string>
|
|
||||||
<key>provisioningProfiles</key>
|
|
||||||
<dict>
|
|
||||||
<key>com.8bit.bitwarden</key>
|
|
||||||
<string>Ad hoc: Bitwarden 2021</string>
|
|
||||||
<key>com.8bit.bitwarden.autofill</key>
|
|
||||||
<string>Ad hoc: Autofill 2021</string>
|
|
||||||
<key>com.8bit.bitwarden.find-login-action-extension</key>
|
|
||||||
<string>Ad hoc: Extension 2021</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
17
.github/resources/export-options-app-store.plist
vendored
17
.github/resources/export-options-app-store.plist
vendored
@@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>method</key>
|
|
||||||
<string>app-store</string>
|
|
||||||
<key>provisioningProfiles</key>
|
|
||||||
<dict>
|
|
||||||
<key>com.8bit.bitwarden</key>
|
|
||||||
<string>Dist: Bitwarden 2021</string>
|
|
||||||
<key>com.8bit.bitwarden.autofill</key>
|
|
||||||
<string>Dist: Autofill 2021</string>
|
|
||||||
<key>com.8bit.bitwarden.find-login-action-extension</key>
|
|
||||||
<string>Dist: Extension 2021</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
BIN
.github/secrets/app_fdroid-keystore.jks.gpg
vendored
BIN
.github/secrets/app_fdroid-keystore.jks.gpg
vendored
Binary file not shown.
BIN
.github/secrets/app_play-keystore.jks.gpg
vendored
BIN
.github/secrets/app_play-keystore.jks.gpg
vendored
Binary file not shown.
BIN
.github/secrets/app_upload-keystore.jks.gpg
vendored
BIN
.github/secrets/app_upload-keystore.jks.gpg
vendored
Binary file not shown.
BIN
.github/secrets/bitwarden-mobile-key.p12.gpg
vendored
BIN
.github/secrets/bitwarden-mobile-key.p12.gpg
vendored
Binary file not shown.
BIN
.github/secrets/dist_autofill.mobileprovision.gpg
vendored
BIN
.github/secrets/dist_autofill.mobileprovision.gpg
vendored
Binary file not shown.
BIN
.github/secrets/dist_bitwarden.mobileprovision.gpg
vendored
BIN
.github/secrets/dist_bitwarden.mobileprovision.gpg
vendored
Binary file not shown.
BIN
.github/secrets/dist_extension.mobileprovision.gpg
vendored
BIN
.github/secrets/dist_extension.mobileprovision.gpg
vendored
Binary file not shown.
3
.github/secrets/google-services.json.gpg
vendored
3
.github/secrets/google-services.json.gpg
vendored
@@ -1,3 +0,0 @@
|
|||||||
<EFBFBD>
|
|
||||||
K<>Y#<23>(<28><><EFBFBD><EFBFBD>EI߄T?)l<><6C><EFBFBD><18><><10>"=<3D>|<7C>'e<><0E>m<EFBFBD>/~<7E><>'F<><46>><3E><><EFBFBD><EFBFBD>l<EFBFBD>b<EFBFBD>[<5B>+R<><52>iL<69><4C>"<22><><EFBFBD>~V:<3A><>p<EFBFBD>a<17>ڵel%8t<38><74>튖<EFBFBD>y<<3C>n<EFBFBD><6E><EFBFBD>aU<61>w<16>JD<4A><44><1F><>We<57>9<EFBFBD><39><EFBFBD><EFBFBD><x8d<38>O<EFBFBD>j\<14>ד<EFBFBD><D793><EFBFBD>Vq<56><71>
|
|
||||||
Ǻ<EFBFBD>-<2D>#<23><><11><>]$<24>(<28>l,<2C>Br<42><02><>d<><64><EFBFBD>a-<2D><><EFBFBD>:<3A><>:<3A><04>9b,!Em<02><19><>Qf<>D<EFBFBD>g<EFBFBD><06><0E>x(P<>ȡ~<7E><EFBFBD><CDB9> <09><>[<06><>!:<3A>;f<><66>
|
|
||||||
BIN
.github/secrets/iphone-distribution-cert.p12.gpg
vendored
BIN
.github/secrets/iphone-distribution-cert.p12.gpg
vendored
Binary file not shown.
BIN
.github/secrets/play_creds.json.gpg
vendored
BIN
.github/secrets/play_creds.json.gpg
vendored
Binary file not shown.
BIN
.github/secrets/store_fdroid-keystore.jks.gpg
vendored
BIN
.github/secrets/store_fdroid-keystore.jks.gpg
vendored
Binary file not shown.
582
.github/workflows/build.yml
vendored
582
.github/workflows/build.yml
vendored
@@ -1,582 +0,0 @@
|
|||||||
---
|
|
||||||
name: Build
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches-ignore:
|
|
||||||
- 'l10n_master'
|
|
||||||
- 'gh-pages'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
cloc:
|
|
||||||
name: CLOC
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- name: Set up CLOC
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get -y install cloc
|
|
||||||
|
|
||||||
- name: Print lines of code
|
|
||||||
run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML
|
|
||||||
|
|
||||||
|
|
||||||
setup:
|
|
||||||
name: Setup
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
outputs:
|
|
||||||
rc_branch_exists: ${{ steps.branch-check.outputs.rc_branch_exists }}
|
|
||||||
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
|
|
||||||
steps:
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- name: Check if special branches exist
|
|
||||||
id: branch-check
|
|
||||||
run: |
|
|
||||||
if [[ $(git ls-remote --heads origin rc) ]]; then
|
|
||||||
echo "::set-output name=rc_branch_exists::1"
|
|
||||||
else
|
|
||||||
echo "::set-output name=rc_branch_exists::0"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $(git ls-remote --heads origin hotfix) ]]; then
|
|
||||||
echo "::set-output name=hotfix_branch_exists::1"
|
|
||||||
else
|
|
||||||
echo "::set-output name=hotfix_branch_exists::0"
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
|
|
||||||
android:
|
|
||||||
name: Android
|
|
||||||
runs-on: windows-2019
|
|
||||||
needs: setup
|
|
||||||
steps:
|
|
||||||
- name: Set up MSBuild
|
|
||||||
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1
|
|
||||||
|
|
||||||
- name: Print environment
|
|
||||||
run: |
|
|
||||||
nuget help | grep Version
|
|
||||||
msbuild -version
|
|
||||||
dotnet --info
|
|
||||||
echo "GitHub ref: $GITHUB_REF"
|
|
||||||
echo "GitHub event: $GITHUB_EVENT"
|
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- name: Decrypt secrets
|
|
||||||
env:
|
|
||||||
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/secrets
|
|
||||||
|
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
|
||||||
--output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
|
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
|
||||||
--output ./src/Android/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg
|
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
|
||||||
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
|
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
|
||||||
--output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Increment version
|
|
||||||
run: |
|
|
||||||
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Setting Version Code $BUILD_NUMBER"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
|
|
||||||
./src/Android/Properties/AndroidManifest.xml
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Restore packages
|
|
||||||
run: nuget restore
|
|
||||||
|
|
||||||
- name: Run Core tests
|
|
||||||
run: dotnet test test/Core.Test/Core.Test.csproj
|
|
||||||
|
|
||||||
- name: Build Play Store publisher
|
|
||||||
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
|
|
||||||
|
|
||||||
- name: Build for Play Store
|
|
||||||
run: |
|
|
||||||
$configuration = "Release";
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Build $configuration Configuration"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
|
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
- name: Sign for Play Store
|
|
||||||
env:
|
|
||||||
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
|
|
||||||
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Sign Google Play Bundle Release Configuration"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
|
|
||||||
"/p:AndroidSigningKeyAlias=upload" "/p:AndroidSigningKeyPass=$($env:UPLOAD_KEYSTORE_PASSWORD)" `
|
|
||||||
"/p:AndroidSigningKeyStore=$("app_upload-keystore.jks")" `
|
|
||||||
"/p:AndroidSigningStorePass=$($env:UPLOAD_KEYSTORE_PASSWORD)" "/p:AndroidPackageFormat=aab" "/v:quiet"
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Copy Google Play Bundle to project root"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
$signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab");
|
|
||||||
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.aab");
|
|
||||||
|
|
||||||
Copy-Item $signedAabPath $signedAabDestPath
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Sign APK Release Configuration"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
|
|
||||||
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:PLAY_KEYSTORE_PASSWORD)" `
|
|
||||||
"/p:AndroidSigningKeyStore=$("app_play-keystore.jks")" `
|
|
||||||
"/p:AndroidSigningStorePass=$($env:PLAY_KEYSTORE_PASSWORD)" "/v:quiet"
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Copy Release APK to project root"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk");
|
|
||||||
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.apk");
|
|
||||||
|
|
||||||
Copy-Item $signedApkPath $signedApkDestPath
|
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
- name: Upload Play Store .aab artifact
|
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
|
|
||||||
with:
|
|
||||||
name: com.x8bit.bitwarden.aab
|
|
||||||
path: ./com.x8bit.bitwarden.aab
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Upload Play Store .apk artifact
|
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
|
|
||||||
with:
|
|
||||||
name: com.x8bit.bitwarden.apk
|
|
||||||
path: ./com.x8bit.bitwarden.apk
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Deploy to Play Store
|
|
||||||
if: |
|
|
||||||
(github.ref == 'refs/heads/master'
|
|
||||||
&& needs.setup.outputs.rc_branch_exists == 0
|
|
||||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
|
||||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
|
||||||
|| github.ref == 'refs/heads/hotfix'
|
|
||||||
run: |
|
|
||||||
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp2.0/Publisher.dll"
|
|
||||||
CREDS_PATH="$HOME/secrets/play_creds.json"
|
|
||||||
AAB_PATH="$GITHUB_WORKSPACE/com.x8bit.bitwarden.aab"
|
|
||||||
TRACK="internal"
|
|
||||||
|
|
||||||
dotnet $PUBLISHER_PATH $CREDS_PATH $AAB_PATH $TRACK
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
|
|
||||||
f-droid:
|
|
||||||
name: F-Droid Build
|
|
||||||
runs-on: windows-2019
|
|
||||||
steps:
|
|
||||||
- name: Set up MSBuild
|
|
||||||
uses: microsoft/setup-msbuild@c26a08ba26249b81327e26f6ef381897b6a8754d # v1
|
|
||||||
|
|
||||||
- name: Print environment
|
|
||||||
run: |
|
|
||||||
nuget help | grep Version
|
|
||||||
msbuild -version
|
|
||||||
dotnet --info
|
|
||||||
echo "GitHub ref: $GITHUB_REF"
|
|
||||||
echo "GitHub event: $GITHUB_EVENT"
|
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- name: Decrypt secrets
|
|
||||||
env:
|
|
||||||
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/secrets
|
|
||||||
|
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
|
||||||
--output ./src/Android/app_fdroid-keystore.jks ./.github/secrets/app_fdroid-keystore.jks.gpg
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Increment version
|
|
||||||
run: |
|
|
||||||
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Setting Version Code $BUILD_NUMBER"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
sed -i "s/android:versionCode=\"1\"/android:versionCode=\"$BUILD_NUMBER\"/" \
|
|
||||||
./src/Android/Properties/AndroidManifest.xml
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Clean for F-Droid
|
|
||||||
run: |
|
|
||||||
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
|
|
||||||
$appPath = $($env:GITHUB_WORKSPACE + "/src/App/App.csproj");
|
|
||||||
|
|
||||||
$androidManifest = $($env:GITHUB_WORKSPACE + "/src/Android/Properties/AndroidManifest.xml");
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Clean Android and App"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
|
|
||||||
msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Backup project files"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
Copy-Item $androidManifest $($androidManifest + ".original");
|
|
||||||
Copy-Item $androidPath $($androidPath + ".original");
|
|
||||||
Copy-Item $appPath $($appPath + ".original");
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Cleanup Android Manifest"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
$xml=New-Object XML;
|
|
||||||
$xml.Load($androidManifest);
|
|
||||||
|
|
||||||
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
|
|
||||||
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
|
|
||||||
|
|
||||||
$xml.Save($androidManifest);
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Uninstall from Android.csproj"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
$xml=New-Object XML;
|
|
||||||
$xml.Load($androidPath);
|
|
||||||
|
|
||||||
$ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
|
|
||||||
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
|
|
||||||
|
|
||||||
$firebaseNode=$xml.SelectSingleNode(`
|
|
||||||
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
|
|
||||||
$firebaseNode.ParentNode.RemoveChild($firebaseNode);
|
|
||||||
|
|
||||||
$daggerNode=$xml.SelectSingleNode(`
|
|
||||||
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Google.Dagger']", $ns);
|
|
||||||
$daggerNode.ParentNode.RemoveChild($daggerNode);
|
|
||||||
|
|
||||||
$safetyNetNode=$xml.SelectSingleNode(`
|
|
||||||
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
|
|
||||||
$safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
|
|
||||||
|
|
||||||
$xml.Save($androidPath);
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Uninstall from App.csproj"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
$xml=New-Object XML;
|
|
||||||
$xml.Load($appPath);
|
|
||||||
|
|
||||||
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
|
|
||||||
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
|
||||||
|
|
||||||
$xml.Save($appPath);
|
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
- name: Restore packages
|
|
||||||
run: nuget restore
|
|
||||||
|
|
||||||
- name: Build for F-Droid
|
|
||||||
run: |
|
|
||||||
$configuration = "FDroid";
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Build $configuration Configuration"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
|
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
- name: Sign for F-Droid
|
|
||||||
env:
|
|
||||||
FDROID_KEYSTORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Sign FDroid Configuration"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" `
|
|
||||||
"/t:SignAndroidPackage" "/p:Configuration=FDroid" "/p:AndroidKeyStore=true" `
|
|
||||||
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:FDROID_KEYSTORE_PASSWORD)" `
|
|
||||||
"/p:AndroidSigningKeyStore=$("app_fdroid-keystore.jks")" `
|
|
||||||
"/p:AndroidSigningStorePass=$($env:FDROID_KEYSTORE_PASSWORD)" "/v:quiet"
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Copy FDroid apk to project root"
|
|
||||||
Write-Output "########################################"
|
|
||||||
|
|
||||||
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/FDroid/com.x8bit.bitwarden-Signed.apk");
|
|
||||||
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden-fdroid.apk");
|
|
||||||
|
|
||||||
Copy-Item $signedApkPath $signedApkDestPath
|
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
- name: Upload F-Droid .apk artifact
|
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
|
|
||||||
with:
|
|
||||||
name: com.x8bit.bitwarden-fdroid.apk
|
|
||||||
path: ./com.x8bit.bitwarden-fdroid.apk
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
|
|
||||||
ios:
|
|
||||||
name: Apple iOS
|
|
||||||
runs-on: macos-11
|
|
||||||
needs: setup
|
|
||||||
steps:
|
|
||||||
- name: Print environment
|
|
||||||
run: |
|
|
||||||
nuget help | grep Version
|
|
||||||
msbuild -version
|
|
||||||
dotnet --info
|
|
||||||
echo "GitHub ref: $GITHUB_REF"
|
|
||||||
echo "GitHub event: $GITHUB_EVENT"
|
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- name: Decrypt secrets
|
|
||||||
env:
|
|
||||||
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/secrets
|
|
||||||
|
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
|
||||||
--output $HOME/secrets/bitwarden-mobile-key.p12 ./.github/secrets/bitwarden-mobile-key.p12.gpg
|
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
|
||||||
--output $HOME/secrets/iphone-distribution-cert.p12 ./.github/secrets/iphone-distribution-cert.p12.gpg
|
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
|
||||||
--output $HOME/secrets/dist_autofill.mobileprovision ./.github/secrets/dist_autofill.mobileprovision.gpg
|
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
|
||||||
--output $HOME/secrets/dist_bitwarden.mobileprovision ./.github/secrets/dist_bitwarden.mobileprovision.gpg
|
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
|
||||||
--output $HOME/secrets/dist_extension.mobileprovision ./.github/secrets/dist_extension.mobileprovision.gpg
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Increment version
|
|
||||||
run: |
|
|
||||||
BUILD_NUMBER=$((100 + $GITHUB_RUN_NUMBER))
|
|
||||||
|
|
||||||
echo "########################################"
|
|
||||||
echo "##### Setting CFBundleVersion $BUILD_NUMBER"
|
|
||||||
echo "########################################"
|
|
||||||
|
|
||||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS/Info.plist
|
|
||||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
|
|
||||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Set up Keychain
|
|
||||||
env:
|
|
||||||
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
|
|
||||||
MOBILE_KEY_PASSWORD: ${{ secrets.IOS_KEY_PASSWORD }}
|
|
||||||
DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
security create-keychain -p $KEYCHAIN_PASSWORD build.keychain
|
|
||||||
security default-keychain -s build.keychain
|
|
||||||
security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain
|
|
||||||
security set-keychain-settings -lut 1200 build.keychain
|
|
||||||
security import ~/secrets/bitwarden-mobile-key.p12 -k build.keychain -P $MOBILE_KEY_PASSWORD \
|
|
||||||
-T /usr/bin/codesign -T /usr/bin/security
|
|
||||||
security import ~/secrets/iphone-distribution-cert.p12 -k build.keychain -P $DIST_CERT_PASSWORD \
|
|
||||||
-T /usr/bin/codesign -T /usr/bin/security
|
|
||||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Set up provisioning profiles
|
|
||||||
run: |
|
|
||||||
AUTOFILL_PROFILE_PATH=$HOME/secrets/dist_autofill.mobileprovision
|
|
||||||
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision
|
|
||||||
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision
|
|
||||||
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
|
|
||||||
|
|
||||||
mkdir -p "$PROFILES_DIR_PATH"
|
|
||||||
|
|
||||||
AUTOFILL_UUID=$(grep UUID -A1 -a $AUTOFILL_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
|
|
||||||
cp $AUTOFILL_PROFILE_PATH "$PROFILES_DIR_PATH/$AUTOFILL_UUID.mobileprovision"
|
|
||||||
|
|
||||||
BITWARDEN_UUID=$(grep UUID -A1 -a $BITWARDEN_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
|
|
||||||
cp $BITWARDEN_PROFILE_PATH "$PROFILES_DIR_PATH/$BITWARDEN_UUID.mobileprovision"
|
|
||||||
|
|
||||||
EXTENSION_UUID=$(grep UUID -A1 -a $EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
|
|
||||||
cp $EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$EXTENSION_UUID.mobileprovision"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Restore packages
|
|
||||||
run: nuget restore
|
|
||||||
|
|
||||||
- name: Archive Build for App Store
|
|
||||||
run: |
|
|
||||||
$configuration = "AppStore";
|
|
||||||
$platform = "iPhone";
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Archive $configuration Configuration for $platform Platform"
|
|
||||||
Write-Output "########################################"
|
|
||||||
msbuild "$($env:GITHUB_WORKSPACE + "/src/iOS/iOS.csproj")" "/p:Platform=$platform" `
|
|
||||||
"/p:Configuration=$configuration" "/p:ArchiveOnBuild=true" "/t:`"Build`""
|
|
||||||
|
|
||||||
Write-Output "########################################"
|
|
||||||
Write-Output "##### Done"
|
|
||||||
Write-Output "########################################"
|
|
||||||
ls ~/Library/Developer/Xcode/Archives
|
|
||||||
shell: pwsh
|
|
||||||
|
|
||||||
- name: Export .ipa for App Store
|
|
||||||
run: |
|
|
||||||
EXPORT_OPTIONS_PATH="./.github/resources/export-options-app-store.plist"
|
|
||||||
ARCHIVE_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive"
|
|
||||||
EXPORT_PATH="./bitwarden-export"
|
|
||||||
|
|
||||||
xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportPath $EXPORT_PATH \
|
|
||||||
-exportOptionsPlist $EXPORT_OPTIONS_PATH
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Upload App Store .ipa artifact
|
|
||||||
uses: actions/upload-artifact@ee69f02b3dfdecd58bb31b4d133da38ba6fe3700 # v2.2.4
|
|
||||||
with:
|
|
||||||
name: Bitwarden.ipa
|
|
||||||
path: ./bitwarden-export/Bitwarden.ipa
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: Deploy to App Store
|
|
||||||
if: |
|
|
||||||
(github.ref == 'refs/heads/master'
|
|
||||||
&& needs.setup.outputs.rc_branch_exists == 0
|
|
||||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
|
||||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
|
||||||
|| github.ref == 'refs/heads/hotfix'
|
|
||||||
env:
|
|
||||||
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
|
|
||||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
xcrun altool --upload-app --type ios --file "./bitwarden-export/Bitwarden.ipa" \
|
|
||||||
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
|
|
||||||
crowdin-push:
|
|
||||||
name: Crowdin Push
|
|
||||||
if: github.ref == 'refs/heads/master'
|
|
||||||
needs:
|
|
||||||
- android
|
|
||||||
- f-droid
|
|
||||||
- ios
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
env:
|
|
||||||
_CROWDIN_PROJECT_ID: "269690"
|
|
||||||
steps:
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- name: Login to Azure
|
|
||||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
|
||||||
with:
|
|
||||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
|
||||||
|
|
||||||
- name: Retrieve secrets
|
|
||||||
id: retrieve-secrets
|
|
||||||
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
|
||||||
with:
|
|
||||||
keyvault: "bitwarden-prod-kv"
|
|
||||||
secrets: "crowdin-api-token"
|
|
||||||
|
|
||||||
- name: Upload Sources
|
|
||||||
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea # v1.3.2
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
|
||||||
with:
|
|
||||||
config: crowdin.yml
|
|
||||||
crowdin_branch_name: master
|
|
||||||
upload_sources: true
|
|
||||||
upload_translations: false
|
|
||||||
|
|
||||||
|
|
||||||
check-failures:
|
|
||||||
name: Check for failures
|
|
||||||
if: always()
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
needs:
|
|
||||||
- cloc
|
|
||||||
- android
|
|
||||||
- f-droid
|
|
||||||
- ios
|
|
||||||
- crowdin-push
|
|
||||||
steps:
|
|
||||||
- name: Check if any job failed
|
|
||||||
if: |
|
|
||||||
(github.ref == 'refs/heads/master')
|
|
||||||
|| (github.ref == 'refs/heads/rc')
|
|
||||||
|| (github.ref == 'refs/heads/hotfix')
|
|
||||||
env:
|
|
||||||
CLOC_STATUS: ${{ needs.cloc.result }}
|
|
||||||
ANDROID_STATUS: ${{ needs.android.result }}
|
|
||||||
F_DROID_STATUS: ${{ needs.f-droid.result }}
|
|
||||||
IOS_STATUS: ${{ needs.ios.result }}
|
|
||||||
CROWDIN_PUSH_STATUS: ${{ needs.crowdin-push.result }}
|
|
||||||
run: |
|
|
||||||
if [ "$CLOC_STATUS" = "failure" ]; then
|
|
||||||
exit 1
|
|
||||||
elif [ "$ANDROID_STATUS" = "failure" ]; then
|
|
||||||
exit 1
|
|
||||||
elif [ "$F_DROID_STATUS" = "failure" ]; then
|
|
||||||
exit 1
|
|
||||||
elif [ "$IOS_STATUS" = "failure" ]; then
|
|
||||||
exit 1
|
|
||||||
elif [ "$CROWDIN_PUSH_STATUS" = "failure" ]; then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Login to Azure - Prod Subscription
|
|
||||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
|
||||||
if: failure()
|
|
||||||
with:
|
|
||||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
|
||||||
|
|
||||||
- name: Retrieve secrets
|
|
||||||
id: retrieve-secrets
|
|
||||||
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
|
||||||
if: failure()
|
|
||||||
with:
|
|
||||||
keyvault: "bitwarden-prod-kv"
|
|
||||||
secrets: "devops-alerts-slack-webhook-url"
|
|
||||||
|
|
||||||
- name: Notify Slack on failure
|
|
||||||
uses: act10ns/slack@e4e71685b9b239384b0f676a63c32367f59c2522 # v1.2.2
|
|
||||||
if: failure()
|
|
||||||
env:
|
|
||||||
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
|
|
||||||
with:
|
|
||||||
status: ${{ job.status }}
|
|
||||||
49
.github/workflows/crowdin-pull.yml
vendored
49
.github/workflows/crowdin-pull.yml
vendored
@@ -1,49 +0,0 @@
|
|||||||
---
|
|
||||||
name: Crowdin Sync
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs: {}
|
|
||||||
schedule:
|
|
||||||
- cron: '0 0 * * 5'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
crowdin-sync:
|
|
||||||
name: Autosync
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
env:
|
|
||||||
_CROWDIN_PROJECT_ID: "269690"
|
|
||||||
steps:
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- name: Login to Azure
|
|
||||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
|
||||||
with:
|
|
||||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
|
||||||
|
|
||||||
- name: Retrieve secrets
|
|
||||||
id: retrieve-secrets
|
|
||||||
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
|
||||||
with:
|
|
||||||
keyvault: "bitwarden-prod-kv"
|
|
||||||
secrets: "crowdin-api-token"
|
|
||||||
|
|
||||||
- name: Download translations
|
|
||||||
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
|
||||||
with:
|
|
||||||
config: crowdin.yml
|
|
||||||
crowdin_branch_name: master
|
|
||||||
upload_sources: false
|
|
||||||
upload_translations: false
|
|
||||||
download_translations: true
|
|
||||||
github_user_name: "github-actions"
|
|
||||||
github_user_email: "<>"
|
|
||||||
commit_message: "Autosync the updated translations"
|
|
||||||
localization_branch_name: crowdin-auto-sync
|
|
||||||
create_pull_request: true
|
|
||||||
pull_request_title: "Autosync Crowdin Translations"
|
|
||||||
pull_request_body: "Autosync the updated translations"
|
|
||||||
167
.github/workflows/release.yml
vendored
167
.github/workflows/release.yml
vendored
@@ -1,167 +0,0 @@
|
|||||||
---
|
|
||||||
name: Release
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
release_type:
|
|
||||||
description: 'Release Options'
|
|
||||||
required: true
|
|
||||||
default: 'Initial Release'
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- Initial Release
|
|
||||||
- Redeploy
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
name: Create Release
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
outputs:
|
|
||||||
branch-name: ${{ steps.branch.outputs.branch-name }}
|
|
||||||
steps:
|
|
||||||
- name: Branch check
|
|
||||||
run: |
|
|
||||||
if [[ "$GITHUB_REF" != "refs/heads/rc" ]] && [[ "$GITHUB_REF" != "refs/heads/hotfix" ]]; then
|
|
||||||
echo "==================================="
|
|
||||||
echo "[!] Can only release from the 'rc' or 'hotfix' branches"
|
|
||||||
echo "==================================="
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- name: Retrieve Mobile release version
|
|
||||||
id: retrieve-mobile-version
|
|
||||||
run: |
|
|
||||||
ver=$(sed -n -e '/android:versionName/ s/.*\= *//p' ./src/Android/Properties/AndroidManifest.xml | tr -d '"')
|
|
||||||
echo "::set-output name=mobile_version::${ver}"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Check to make sure Mobile release version has been bumped
|
|
||||||
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
latest_ver=$(hub release -L 1 -f '%T')
|
|
||||||
latest_ver=${latest_ver:1}
|
|
||||||
echo "Latest version: $latest_ver"
|
|
||||||
ver=${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
|
||||||
echo "Version: $ver"
|
|
||||||
if [ "$latest_ver" = "$ver" ]; then
|
|
||||||
echo "Version has not been bumped!"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Get branch name
|
|
||||||
id: branch
|
|
||||||
run: |
|
|
||||||
BRANCH_NAME=$(basename ${{ github.ref }})
|
|
||||||
echo "::set-output name=branch-name::$BRANCH_NAME"
|
|
||||||
|
|
||||||
- name: Download all artifacts
|
|
||||||
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0
|
|
||||||
with:
|
|
||||||
workflow: build.yml
|
|
||||||
workflow_conclusion: success
|
|
||||||
branch: ${{ steps.branch.outputs.branch-name }}
|
|
||||||
|
|
||||||
- name: Create release
|
|
||||||
uses: ncipollo/release-action@95215a3cb6e6a1908b3c44e00b4fdb15548b1e09 # v2.8.5
|
|
||||||
with:
|
|
||||||
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
|
|
||||||
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
|
|
||||||
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
|
|
||||||
./Bitwarden.ipa/Bitwarden.ipa"
|
|
||||||
commit: ${{ github.sha }}
|
|
||||||
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
|
||||||
name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
|
||||||
body: "<insert release notes here>"
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
draft: true
|
|
||||||
|
|
||||||
|
|
||||||
f-droid:
|
|
||||||
name: F-Droid Release
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
needs: release
|
|
||||||
steps:
|
|
||||||
- name: Checkout repo
|
|
||||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
|
||||||
|
|
||||||
- name: Download F-Droid .apk artifact
|
|
||||||
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74 # v2.14.0
|
|
||||||
with:
|
|
||||||
workflow: build.yml
|
|
||||||
workflow_conclusion: success
|
|
||||||
branch: ${{ needs.release.outputs.branch-name }}
|
|
||||||
name: com.x8bit.bitwarden-fdroid.apk
|
|
||||||
|
|
||||||
- name: Set up Node
|
|
||||||
uses: actions/setup-node@46071b5c7a2e0c34e49c3cb8a0e792e86e18d5ea # v2.3.0
|
|
||||||
with:
|
|
||||||
node-version: '10.x'
|
|
||||||
|
|
||||||
- name: Set up F-Droid server
|
|
||||||
run: |
|
|
||||||
sudo apt-get -qq update
|
|
||||||
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
|
|
||||||
|
|
||||||
- name: Set up Git credentials
|
|
||||||
env:
|
|
||||||
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
|
||||||
run: |
|
|
||||||
git config --global credential.helper store
|
|
||||||
echo "https://${ACCESS_TOKEN}:x-oauth-basic@github.com" >> ~/.git-credentials
|
|
||||||
git config --global user.email "ci@bitwarden.com"
|
|
||||||
git config --global user.name "Bitwarden CI"
|
|
||||||
|
|
||||||
- name: Print environment
|
|
||||||
run: |
|
|
||||||
node --version
|
|
||||||
npm --version
|
|
||||||
git --version
|
|
||||||
echo "GitHub ref: $GITHUB_REF"
|
|
||||||
echo "GitHub event: $GITHUB_EVENT"
|
|
||||||
|
|
||||||
- name: Install Node dependencies
|
|
||||||
run: npm install
|
|
||||||
|
|
||||||
- name: Decrypt secrets
|
|
||||||
env:
|
|
||||||
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/secrets
|
|
||||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
|
||||||
--output ./store/fdroid/keystore.jks ./.github/secrets/store_fdroid-keystore.jks.gpg
|
|
||||||
|
|
||||||
- name: Compile for F-Droid Store
|
|
||||||
env:
|
|
||||||
FDROID_STORE_KEYSTORE_PASSWORD: ${{ secrets.FDROID_STORE_KEYSTORE_PASSWORD }}
|
|
||||||
run: |
|
|
||||||
cd $GITHUB_WORKSPACE
|
|
||||||
mkdir dist
|
|
||||||
cp CNAME ./dist
|
|
||||||
cd store
|
|
||||||
chmod 600 fdroid/config.py fdroid/keystore.jks
|
|
||||||
mkdir -p temp/fdroid
|
|
||||||
TEMP_DIR="$GITHUB_WORKSPACE/store/temp/fdroid"
|
|
||||||
cd fdroid
|
|
||||||
echo "keypass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
|
|
||||||
echo "keystorepass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
|
|
||||||
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
|
|
||||||
mkdir -p repo
|
|
||||||
mv $GITHUB_WORKSPACE/com.x8bit.bitwarden-fdroid.apk ./repo/
|
|
||||||
fdroid update
|
|
||||||
fdroid server update
|
|
||||||
cd ..
|
|
||||||
rm -rf temp/fdroid/archive
|
|
||||||
mv -v temp/fdroid ../dist
|
|
||||||
cd fdroid
|
|
||||||
cp index.html btn.png qr.png ../../dist/fdroid
|
|
||||||
cd $GITHUB_WORKSPACE
|
|
||||||
|
|
||||||
- name: Deploy to gh-pages
|
|
||||||
run: npm run deploy
|
|
||||||
83
.github/workflows/version-bump.yml
vendored
83
.github/workflows/version-bump.yml
vendored
@@ -1,83 +0,0 @@
|
|||||||
---
|
|
||||||
name: Version Bump
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
version_number:
|
|
||||||
description: "New Version"
|
|
||||||
required: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
bump_version:
|
|
||||||
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
|
|
||||||
runs-on: ubuntu-20.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout Branch
|
|
||||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
|
||||||
|
|
||||||
- name: Create Version Branch
|
|
||||||
run: |
|
|
||||||
git switch -c version_bump_${{ github.event.inputs.version_number }}
|
|
||||||
git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
|
||||||
|
|
||||||
- name: Checkout Version Branch
|
|
||||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
|
||||||
with:
|
|
||||||
ref: version_bump_${{ github.event.inputs.version_number }}
|
|
||||||
|
|
||||||
- name: Bump Version - Android XML
|
|
||||||
uses: bitwarden/gh-actions/version-bump@0c263b3963211ccaf5804313c3b3a0bcc52d4b19
|
|
||||||
with:
|
|
||||||
version: ${{ github.event.inputs.version_number }}
|
|
||||||
file_path: "./src/Android/Properties/AndroidManifest.xml"
|
|
||||||
|
|
||||||
- name: Bump Version - iOS.Autofill
|
|
||||||
uses: bitwarden/gh-actions/version-bump@0c263b3963211ccaf5804313c3b3a0bcc52d4b19
|
|
||||||
with:
|
|
||||||
version: ${{ github.event.inputs.version_number }}
|
|
||||||
file_path: "./src/iOS.Autofill/Info.plist"
|
|
||||||
|
|
||||||
- name: Bump Version - iOS.Extension
|
|
||||||
uses: bitwarden/gh-actions/version-bump@0c263b3963211ccaf5804313c3b3a0bcc52d4b19
|
|
||||||
with:
|
|
||||||
version: ${{ github.event.inputs.version_number }}
|
|
||||||
file_path: "./src/iOS.Extension/Info.plist"
|
|
||||||
|
|
||||||
- name: Bump Version - iOS
|
|
||||||
uses: bitwarden/gh-actions/version-bump@0c263b3963211ccaf5804313c3b3a0bcc52d4b19
|
|
||||||
with:
|
|
||||||
version: ${{ github.event.inputs.version_number }}
|
|
||||||
file_path: "./src/iOS/Info.plist"
|
|
||||||
|
|
||||||
- name: Commit files
|
|
||||||
run: |
|
|
||||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
||||||
git config --local user.name "github-actions[bot]"
|
|
||||||
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
|
|
||||||
|
|
||||||
- name: Push changes
|
|
||||||
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
|
||||||
|
|
||||||
- name: Create Version PR
|
|
||||||
env:
|
|
||||||
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
|
|
||||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
|
||||||
BASE_BRANCH: master
|
|
||||||
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
|
|
||||||
run: |
|
|
||||||
gh pr create --title "$TITLE" \
|
|
||||||
--base "$BASE" \
|
|
||||||
--head "$PR_BRANCH" \
|
|
||||||
--label "version update" \
|
|
||||||
--label "automated pr" \
|
|
||||||
--body "
|
|
||||||
## Type of change
|
|
||||||
- [ ] Bug fix
|
|
||||||
- [ ] New feature development
|
|
||||||
- [ ] Tech debt (refactoring, code cleanup, dependency upgrades, etc)
|
|
||||||
- [ ] Build/deploy pipeline (DevOps)
|
|
||||||
- [X] Other
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
Automated version bump to ${{ github.event.inputs.version_number }}"
|
|
||||||
21
.gitignore
vendored
21
.gitignore
vendored
@@ -1,28 +1,15 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
## files generated by popular Visual Studio add-ons.
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
# Visual Studio (>=2015) project-specific, machine local files
|
|
||||||
.vs/
|
|
||||||
|
|
||||||
# JetBrains Rider project-specific, machine local files
|
|
||||||
.idea
|
|
||||||
|
|
||||||
# User-specific files
|
# User-specific files
|
||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
*.userosscache
|
*.userosscache
|
||||||
*.sln.docstates
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
*.userprefs
|
*.userprefs
|
||||||
|
|
||||||
# ignore Xamarin.Android Resource.Designer.cs files
|
|
||||||
**/*.Droid/**/[Rr]esource.[Dd]esigner.cs
|
|
||||||
**/*.Android/**/[Rr]esource.[Dd]esigner.cs
|
|
||||||
**/Android/**/[Rr]esource.[Dd]esigner.cs
|
|
||||||
**/Droid/**/[Rr]esource.[Dd]esigner.cs
|
|
||||||
|
|
||||||
# Xamarin Components
|
|
||||||
Components/
|
|
||||||
|
|
||||||
# Build results
|
# Build results
|
||||||
[Dd]ebug/
|
[Dd]ebug/
|
||||||
[Dd]ebugPublic/
|
[Dd]ebugPublic/
|
||||||
@@ -35,6 +22,9 @@ bld/
|
|||||||
[Bb]in/
|
[Bb]in/
|
||||||
[Oo]bj/
|
[Oo]bj/
|
||||||
|
|
||||||
|
# Visual Studo 2015 cache/options directory
|
||||||
|
.vs/
|
||||||
|
|
||||||
# MSTest test Results
|
# MSTest test Results
|
||||||
[Tt]est[Rr]esult*/
|
[Tt]est[Rr]esult*/
|
||||||
[Bb]uild[Ll]og.*
|
[Bb]uild[Ll]og.*
|
||||||
@@ -208,4 +198,3 @@ FakesAssemblies/
|
|||||||
# Other
|
# Other
|
||||||
project.lock.json
|
project.lock.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
src/App/Css
|
|
||||||
@@ -1,31 +1,4 @@
|
|||||||
# How to Contribute
|
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||||
|
|
||||||
Contributions of all kinds are welcome!
|
|
||||||
|
|
||||||
Please visit our [Community Forums](https://community.bitwarden.com/) for general community discussion and the development roadmap.
|
|
||||||
|
|
||||||
Here is how you can get involved:
|
|
||||||
|
|
||||||
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
|
|
||||||
|
|
||||||
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
|
|
||||||
|
|
||||||
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
|
|
||||||
|
|
||||||
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
|
|
||||||
|
|
||||||
* **Help other users:** Go to the [User-to-User Support category](https://community.bitwarden.com/c/support/) on the Community Forums
|
|
||||||
|
|
||||||
* **Translate:** See the localization (i10n) section below
|
|
||||||
|
|
||||||
## Contributor Agreement
|
|
||||||
|
|
||||||
Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/mobile) if you intend on contributing to any Github repository. Pull requests cannot be accepted and merged unless the author has signed the Contributor Agreement.
|
|
||||||
|
|
||||||
## Pull Request Guidelines
|
|
||||||
|
|
||||||
* commit any pull requests against the `master` branch
|
|
||||||
* include a link to your Community Forums post
|
|
||||||
|
|
||||||
# Localization (l10n)
|
# Localization (l10n)
|
||||||
|
|
||||||
@@ -33,8 +6,8 @@ Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/mobil
|
|||||||
|
|
||||||
We use a translation tool called [Crowdin](https://crowdin.com) to help manage our localization efforts across many different languages.
|
We use a translation tool called [Crowdin](https://crowdin.com) to help manage our localization efforts across many different languages.
|
||||||
|
|
||||||
If you are interested in helping translate the Bitwarden mobile app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-mobile
|
If you are interested in helping translate the bitwarden mobile app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-mobile
|
||||||
|
|
||||||
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/kspearrin).
|
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/mail/compose/kspearrin).
|
||||||
|
|
||||||
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/
|
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/
|
||||||
|
|||||||
23
README.md
23
README.md
@@ -1,31 +1,30 @@
|
|||||||
[](https://github.com/bitwarden/mobile/actions/workflows/build.yml?query=branch:master)
|
[](https://ci.appveyor.com/project/bitwarden/mobile)
|
||||||
[](https://crowdin.com/project/bitwarden-mobile)
|
[](https://crowdin.com/project/bitwarden-mobile)
|
||||||
[](https://gitter.im/bitwarden/Lobby)
|
[](https://gitter.im/bitwarden/Lobby)
|
||||||
|
|
||||||
# Bitwarden Mobile Application
|
# bitwarden mobile
|
||||||
|
|
||||||
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://mobileapp.bitwarden.com/fdroid/" target="_blank"><img alt="Get it on Google Play" src="https://i.imgur.com/HDicnzz.png" width="154" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a>
|
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a> <a href="https://www.amazon.com/dp/B06XMYGPMV" target="_blank"><img src="https://imgur.com/f75uYeM.png" width="132" height="45"></a>
|
||||||
|
|
||||||
The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, and Xamarin Forms.
|
The bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, and Xamarin Forms.
|
||||||
|
|
||||||
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-android-myvault.png" alt="" width="325" height="650" /> <img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-ios-myvault.png" alt="" width="300" height="650" />
|
<img src="https://i.imgur.com/Ty8wkSO.png" alt="" width="300" height="533" /> <img src="https://i.imgur.com/BYb4gVc.png" alt="" width="300" height="533" />
|
||||||
|
|
||||||
# Build/Run
|
# Build/Run
|
||||||
|
|
||||||
**Requirements**
|
**Requirements**
|
||||||
|
|
||||||
- [Visual Studio](https://visualstudio.microsoft.com/)
|
- [Visual Studio w/ Xamarin -or- Xamarin Studio](https://store.xamarin.com/)
|
||||||
- [Xamarin](https://docs.microsoft.com/en-us/xamarin/get-started/installation/?pivots=windows)
|
|
||||||
|
|
||||||
**Run the app**
|
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 app 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")`).
|
||||||
|
|
||||||
- Open the solution file in Visual Studio.
|
After restoring the nuget packages, you can now build and run the app.
|
||||||
- Restore the nuget packages.
|
|
||||||
- Build and run the app.
|
|
||||||
|
|
||||||
# Contribute
|
# Contribute
|
||||||
|
|
||||||
Code contributions are welcome! Visual Studio with Xamarin is required to work on this project. Please commit any pull requests against the `master` branch.
|
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.
|
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. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
|
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
|
||||||
|
|||||||
16
SECURITY.md
16
SECURITY.md
@@ -1,4 +1,4 @@
|
|||||||
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
|
bitwarden believes that working with security researchers across the globe is crucial to keeping our
|
||||||
users safe. If you believe you've found a security issue in our product or service, we encourage you to
|
users safe. If you believe you've found a security issue in our product or service, we encourage you to
|
||||||
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
|
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
|
|||||||
|
|
||||||
# In-scope
|
# In-scope
|
||||||
|
|
||||||
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
|
- Security issues in any current release of bitwarden. This includes the web vault, browser extension,
|
||||||
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
|
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
|
||||||
code is available at https://github.com/bitwarden.
|
code is available at https://github.com/bitwarden.
|
||||||
|
|
||||||
@@ -24,14 +24,14 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
|
|||||||
|
|
||||||
The following bug classes are out-of scope:
|
The following bug classes are out-of scope:
|
||||||
|
|
||||||
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
|
- Bugs that are already reported on any of bitwarden's issue trackers (https://github.com/bitwarden),
|
||||||
or that we already know of. Note that some of our issue tracking is private.
|
or that we already know of. Note that some of our issue tracking is private.
|
||||||
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
|
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
|
||||||
upstream maintainer.
|
upstream maintainer.
|
||||||
- Attacks requiring physical access to a user's device.
|
- Attacks requiring physical access to a user's device.
|
||||||
- Self-XSS
|
- Self-XSS
|
||||||
- Issues related to software or protocols not under Bitwarden's control
|
- Issues related to software or protocols not under bitwarden's control
|
||||||
- Vulnerabilities in outdated versions of Bitwarden
|
- Vulnerabilities in outdated versions of bitwarden
|
||||||
- Missing security best practices that do not directly lead to a vulnerability
|
- Missing security best practices that do not directly lead to a vulnerability
|
||||||
- Issues that do not have any impact on the general public
|
- Issues that do not have any impact on the general public
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ While researching, we'd like to ask you to refrain from:
|
|||||||
|
|
||||||
- Denial of service
|
- Denial of service
|
||||||
- Spamming
|
- Spamming
|
||||||
- Social engineering (including phishing) of Bitwarden staff or contractors
|
- Social engineering (including phishing) of bitwarden staff or contractors
|
||||||
- Any physical attempts against Bitwarden property or data centers
|
- Any physical attempts against bitwarden property or data centers
|
||||||
|
|
||||||
Thank you for helping keep Bitwarden and our users safe!
|
Thank you for helping keep bitwarden and our users safe!
|
||||||
|
|||||||
18
appveyor.yml
Normal file
18
appveyor.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
skip_tags: true
|
||||||
|
before_build:
|
||||||
|
- nuget restore
|
||||||
|
- IF DEFINED keystore_dec_secret nuget install secure-file -ExcludeVersion
|
||||||
|
after_build:
|
||||||
|
- ps: IF($env:keystore_dec_secret) { .\src\Android\increment-version.ps1 $($env:APPVEYOR_BUILD_FOLDER) $($env:APPVEYOR_BUILD_NUMBER) }
|
||||||
|
- IF DEFINED keystore_dec_secret secure-file\tools\secure-file -decrypt src\Android\8bit.keystore.enc -secret %keystore_dec_secret%
|
||||||
|
- IF DEFINED keystore_password msbuild "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" "/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=%keystore_password%" "/p:AndroidSigningKeyStore=8bit.keystore" "/p:AndroidSigningStorePass=%keystore_password%" "src\Android\Android.csproj"
|
||||||
|
- ps: IF($env:keystore_dec_secret) { copy-item src\Android\bin\Release\com.x8bit.bitwarden-Signed.apk .\com.x8bit.bitwarden-$($env:APPVEYOR_BUILD_NUMBER).apk }
|
||||||
|
on_success:
|
||||||
|
- IF DEFINED play_dec_secret secure-file\tools\secure-file -decrypt store\google\Publisher\play_creds.json.enc -secret %play_dec_secret%
|
||||||
|
- IF DEFINED play_dec_secret store\google\Publisher\bin\Debug\Publisher.exe %APPVEYOR_BUILD_FOLDER%\store\google\Publisher\play_creds.json %APPVEYOR_BUILD_FOLDER%\src\Android\bin\Release\com.x8bit.bitwarden-Signed.apk alpha
|
||||||
|
artifacts:
|
||||||
|
- path: com.x8bit.bitwarden-%APPVEYOR_BUILD_NUMBER%.apk
|
||||||
|
branches:
|
||||||
|
except:
|
||||||
|
- l10n_master
|
||||||
|
image: Visual Studio 2017
|
||||||
@@ -1,438 +1,484 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 16
|
# Visual Studio 15
|
||||||
VisualStudioVersion = 16.0.29009.5
|
VisualStudioVersion = 15.0.26430.13
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android", "src\Android\Android.csproj", "{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android", "src\Android\Android.csproj", "{04B18ED2-B76D-4947-8474-191F8FD2B5E0}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App", "src\App\App.csproj", "{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS", "src\iOS\iOS.csproj", "{1F78403F-9A28-405B-9289-B9DBEB55F074}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "src\Core\Core.csproj", "{4B8A8C41-9820-4341-974C-41E65B7F4366}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "src\App\App.csproj", "{B490C5DA-639E-4994-ABD2-54222B8A348E}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Playground", "test\Playground\Playground.csproj", "{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0D790714-ECF8-4A83-BE4A-E9C84DD1BB5D}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D10CA4A9-F866-40E1-B658-F69051236C71}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EC730FD9-F623-4B6C-B503-95CDCFBCF277}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{8904C536-C67D-420F-9971-51B26574C3AA}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App.Test", "test\App.Test\App.Test.csproj", "{A300DCE1-8D10-4267-B96A-CB01AEB7C220}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Extension", "src\iOS.Extension\iOS.Extension.csproj", "{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{B2538ADA-B605-4D6F-ACD2-62A409680F84}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Test", "test\iOS.Test\iOS.Test.csproj", "{6702027A-F726-4149-863E-7CB924674B9A}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android.Test", "test\Android.Test\Android.Test.csproj", "{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "store", "store", "{92470CBD-9047-4C3C-8EA3-D972D6622D84}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "store", "store", "{92470CBD-9047-4C3C-8EA3-D972D6622D84}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "google", "google", "{2E399654-26A2-46F6-B9CA-1B496A3F370A}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "google", "google", "{2E399654-26A2-46F6-B9CA-1B496A3F370A}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{76690DFB-B7F4-4781-83E4-113FDC450AFE}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Publisher", "store\google\Publisher\Publisher.csproj", "{428CACAB-CC26-4F41-9062-1E4A9BC82640}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
|
||||||
.editorconfig = .editorconfig
|
|
||||||
.gitignore = .gitignore
|
|
||||||
.github\workflows\build.yml = .github\workflows\build.yml
|
|
||||||
CONTRIBUTING.md = CONTRIBUTING.md
|
|
||||||
crowdin.yml = crowdin.yml
|
|
||||||
README.md = README.md
|
|
||||||
SECURITY.md = SECURITY.md
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Publisher", "store\google\Publisher\Publisher.csproj", "{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{E71F3053-056C-4381-9638-048ED73BDFF6}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS", "src\iOS\iOS.csproj", "{599E0201-420A-4C3E-A7BA-5349F72E0B15}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Extension", "src\iOS.Extension\iOS.Extension.csproj", "{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Autofill", "src\iOS.Autofill\iOS.Autofill.csproj", "{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "test\Common\Common.csproj", "{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Test", "test\Core.Test\Core.Test.csproj", "{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}"
|
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
||||||
|
Ad-Hoc|ARM = Ad-Hoc|ARM
|
||||||
Ad-Hoc|iPhone = Ad-Hoc|iPhone
|
Ad-Hoc|iPhone = Ad-Hoc|iPhone
|
||||||
Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator
|
Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator
|
||||||
|
Ad-Hoc|x64 = Ad-Hoc|x64
|
||||||
|
Ad-Hoc|x86 = Ad-Hoc|x86
|
||||||
AppStore|Any CPU = AppStore|Any CPU
|
AppStore|Any CPU = AppStore|Any CPU
|
||||||
|
AppStore|ARM = AppStore|ARM
|
||||||
AppStore|iPhone = AppStore|iPhone
|
AppStore|iPhone = AppStore|iPhone
|
||||||
AppStore|iPhoneSimulator = AppStore|iPhoneSimulator
|
AppStore|iPhoneSimulator = AppStore|iPhoneSimulator
|
||||||
|
AppStore|x64 = AppStore|x64
|
||||||
|
AppStore|x86 = AppStore|x86
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|ARM = Debug|ARM
|
||||||
Debug|iPhone = Debug|iPhone
|
Debug|iPhone = Debug|iPhone
|
||||||
Debug|iPhoneSimulator = Debug|iPhoneSimulator
|
Debug|iPhoneSimulator = Debug|iPhoneSimulator
|
||||||
FDroid|Any CPU = FDroid|Any CPU
|
Debug|x64 = Debug|x64
|
||||||
FDroid|iPhone = FDroid|iPhone
|
Debug|x86 = Debug|x86
|
||||||
FDroid|iPhoneSimulator = FDroid|iPhoneSimulator
|
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|ARM = Release|ARM
|
||||||
Release|iPhone = Release|iPhone
|
Release|iPhone = Release|iPhone
|
||||||
Release|iPhoneSimulator = Release|iPhoneSimulator
|
Release|iPhoneSimulator = Release|iPhoneSimulator
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhone.Deploy.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|ARM.Deploy.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x64.Build.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x64.Deploy.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|Any CPU.Deploy.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x86.Build.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhone.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x86.Deploy.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhone.Deploy.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|ARM.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|ARM.Build.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|ARM.Deploy.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhone.Build.0 = Debug|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x64.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhone.Deploy.0 = Debug|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x64.Build.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x64.Deploy.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x86.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x86.Build.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x86.Deploy.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|Any CPU.Deploy.0 = FDroid|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|iPhone.Deploy.0 = FDroid|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|ARM.Deploy.0 = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|iPhoneSimulator.Deploy.0 = FDroid|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x64.Deploy.0 = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhone.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhone.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhone.Deploy.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x86.Deploy.0 = Debug|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|ARM.Deploy.0 = Release|Any CPU
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.Deploy.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x64.Deploy.0 = Release|Any CPU
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x86.Deploy.0 = Release|Any CPU
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.Deploy.0 = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.Build.0 = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.Deploy.0 = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|ARM.ActiveCfg = AppStore|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhone.Build.0 = Debug|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhone.Deploy.0 = Debug|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|x64.ActiveCfg = AppStore|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|x86.ActiveCfg = AppStore|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|ARM.ActiveCfg = Debug|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|x64.ActiveCfg = Debug|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|Any CPU.Build.0 = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|x64.Build.0 = Debug|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|x86.ActiveCfg = Debug|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhone.ActiveCfg = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|x86.Build.0 = Debug|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhone.Build.0 = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|Any CPU.ActiveCfg = Release|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhone.Deploy.0 = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|ARM.ActiveCfg = Release|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|iPhone.ActiveCfg = Release|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|iPhone.Build.0 = Release|iPhone
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|x64.ActiveCfg = Release|iPhone
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|x86.ActiveCfg = Release|iPhone
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhone.Build.0 = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|x64.Build.0 = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|x86.Build.0 = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhone.Build.0 = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|ARM.ActiveCfg = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|ARM.Build.0 = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|x64.ActiveCfg = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|x64.Build.0 = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|Any CPU.Build.0 = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|x86.ActiveCfg = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|iPhone.ActiveCfg = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|x86.Build.0 = Release|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|iPhone.Build.0 = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|iPhone.Build.0 = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|iPhone.ActiveCfg = Release|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|iPhone.Build.0 = Release|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|x64.Build.0 = Release|Any CPU
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|x86.Build.0 = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|ARM.ActiveCfg = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|ARM.Build.0 = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|x64.ActiveCfg = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|x64.Build.0 = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|x86.ActiveCfg = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|x86.Build.0 = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Debug|iPhone.Build.0 = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhone.ActiveCfg = Release|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhone.Build.0 = Release|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.Build.0 = AppStore|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|ARM.ActiveCfg = AppStore|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.Build.0 = Debug|iPhone
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|x64.ActiveCfg = AppStore|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|x86.ActiveCfg = AppStore|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|ARM.ActiveCfg = Debug|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|x64.ActiveCfg = Debug|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|x64.Build.0 = Debug|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|Any CPU.Build.0 = Release|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|x86.ActiveCfg = Debug|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|iPhone.ActiveCfg = Release|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|x86.Build.0 = Debug|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|iPhone.Build.0 = Release|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|Any CPU.ActiveCfg = Release|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|ARM.ActiveCfg = Release|iPhone
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|iPhone.ActiveCfg = Release|iPhone
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|iPhone.Build.0 = Release|iPhone
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|x64.ActiveCfg = Release|iPhone
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|x86.ActiveCfg = Release|iPhone
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.Build.0 = Debug|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.Deploy.0 = Debug|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|x64.Build.0 = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.FDroid|Any CPU.ActiveCfg = FDroid|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|x86.Build.0 = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.FDroid|iPhone.ActiveCfg = FDroid|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.FDroid|iPhone.Build.0 = FDroid|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|ARM.ActiveCfg = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.FDroid|iPhoneSimulator.Build.0 = FDroid|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|ARM.Build.0 = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|Any CPU.ActiveCfg = Release|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhone.ActiveCfg = Release|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhone.Build.0 = Release|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|x64.ActiveCfg = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|x64.Build.0 = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|x86.ActiveCfg = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|x86.Build.0 = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.AppStore|Any CPU.Build.0 = AppStore|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhone.Build.0 = Debug|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhone.Deploy.0 = Debug|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.ActiveCfg = Release|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.Build.0 = Release|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|iPhone.ActiveCfg = Release|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|iPhone.Build.0 = Release|iPhone
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|Any CPU.ActiveCfg = Release|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|iPhone.ActiveCfg = Release|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|iPhone.Build.0 = Release|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|ARM.ActiveCfg = AppStore|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.AppStore|Any CPU.Build.0 = AppStore|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|x64.ActiveCfg = AppStore|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|x86.ActiveCfg = AppStore|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|ARM.ActiveCfg = Debug|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhone.Build.0 = Debug|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhone.Deploy.0 = Debug|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|x64.ActiveCfg = Debug|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|x86.ActiveCfg = Debug|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|Any CPU.ActiveCfg = Release|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Release|Any CPU.ActiveCfg = Release|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|Any CPU.Build.0 = Release|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Release|ARM.ActiveCfg = Release|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|iPhone.ActiveCfg = Release|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Release|iPhone.ActiveCfg = Release|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|iPhone.Build.0 = Release|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Release|iPhone.Build.0 = Release|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
{6702027A-F726-4149-863E-7CB924674B9A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
{6702027A-F726-4149-863E-7CB924674B9A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|Any CPU.ActiveCfg = Release|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Release|x64.ActiveCfg = Release|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhone.ActiveCfg = Release|iPhone
|
{6702027A-F726-4149-863E-7CB924674B9A}.Release|x86.ActiveCfg = Release|iPhone
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhone.Build.0 = Release|iPhone
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|ARM.Deploy.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|iPhone.Deploy.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.AppStore|iPhone.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|x64.Build.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|x64.Deploy.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|x86.Build.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Ad-Hoc|x86.Deploy.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Debug|iPhone.Build.0 = Debug|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.FDroid|Any CPU.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|ARM.ActiveCfg = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.FDroid|Any CPU.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|ARM.Build.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.FDroid|iPhone.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|ARM.Deploy.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.FDroid|iPhone.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.FDroid|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.FDroid|iPhoneSimulator.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|iPhone.Deploy.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Release|Any CPU.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Release|iPhone.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Release|iPhone.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|x64.ActiveCfg = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|x64.Build.0 = Release|Any CPU
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|x64.Deploy.0 = Release|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|x86.ActiveCfg = Release|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|x86.Build.0 = Release|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.AppStore|x86.Deploy.0 = Release|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|ARM.Deploy.0 = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.AppStore|iPhone.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|iPhone.Deploy.0 = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Debug|iPhone.Build.0 = Debug|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|x64.Deploy.0 = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.FDroid|Any CPU.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.FDroid|Any CPU.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.FDroid|iPhone.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Debug|x86.Deploy.0 = Debug|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.FDroid|iPhone.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.FDroid|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.FDroid|iPhoneSimulator.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Release|Any CPU.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|ARM.Build.0 = Release|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Release|iPhone.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|ARM.Deploy.0 = Release|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Release|iPhone.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|iPhone.Build.0 = Release|Any CPU
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|iPhone.Deploy.0 = Release|Any CPU
|
||||||
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||||
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||||
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
||||||
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|x64.Deploy.0 = Release|Any CPU
|
||||||
|
{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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
{04B18ED2-B76D-4947-8474-191F8FD2B5E0} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
{1F78403F-9A28-405B-9289-B9DBEB55F074} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||||
{4B8A8C41-9820-4341-974C-41E65B7F4366} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
{B490C5DA-639E-4994-ABD2-54222B8A348E} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3} = {8904C536-C67D-420F-9971-51B26574C3AA}
|
{A300DCE1-8D10-4267-B96A-CB01AEB7C220} = {0D790714-ECF8-4A83-BE4A-E9C84DD1BB5D}
|
||||||
|
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||||
|
{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}
|
{2E399654-26A2-46F6-B9CA-1B496A3F370A} = {92470CBD-9047-4C3C-8EA3-D972D6622D84}
|
||||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D} = {2E399654-26A2-46F6-B9CA-1B496A3F370A}
|
{428CACAB-CC26-4F41-9062-1E4A9BC82640} = {2E399654-26A2-46F6-B9CA-1B496A3F370A}
|
||||||
{E71F3053-056C-4381-9638-048ED73BDFF6} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
|
||||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
|
||||||
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
|
||||||
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
|
||||||
{4085B0A5-12A9-4993-B8B8-4ACE72E62E39} = {8904C536-C67D-420F-9971-51B26574C3AA}
|
|
||||||
{8AE548D9-A567-4E97-995E-93EC7DB0FDE0} = {8904C536-C67D-420F-9971-51B26574C3AA}
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {7D436EA3-8B7E-45D2-8D14-0730BD2E0410}
|
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
23
crowdin.yml
23
crowdin.yml
@@ -1,40 +1,33 @@
|
|||||||
project_id_env: _CROWDIN_PROJECT_ID
|
|
||||||
api_token_env: CROWDIN_API_TOKEN
|
|
||||||
preserve_hierarchy: true
|
|
||||||
files:
|
files:
|
||||||
- source: /src/App/Resources/AppResources.resx
|
- source: /src/App/Resources/AppResources.resx
|
||||||
dest: /src/App/Resources/%original_file_name%
|
|
||||||
translation: /src/App/Resources/AppResources.%two_letters_code%.resx
|
translation: /src/App/Resources/AppResources.%two_letters_code%.resx
|
||||||
update_option: update_as_unapproved
|
|
||||||
languages_mapping:
|
languages_mapping:
|
||||||
two_letters_code:
|
two_letters_code:
|
||||||
zh-CN: zh-Hans
|
zh-CN: zh-Hans
|
||||||
zh-TW: zh-Hant
|
zh-TW: zh-Hant
|
||||||
pt-PT: pt-PT
|
pt-PT: pt-PT
|
||||||
pt-BR: pt-BR
|
pt-BR: pt-BR
|
||||||
en-GB: en-GB
|
- source: /store/amazon/en/copy.resx
|
||||||
en-IN: en-IN
|
translation: /store/amazon/%two_letters_code%/copy.resx
|
||||||
|
languages_mapping:
|
||||||
|
two_letters_code:
|
||||||
|
zh-CN: zh-Hans
|
||||||
|
zh-TW: zh-Hant
|
||||||
|
pt-PT: pt-PT
|
||||||
|
pt-BR: pt-BR
|
||||||
- source: /store/apple/en/copy.resx
|
- source: /store/apple/en/copy.resx
|
||||||
dest: /store/apple/en/%original_file_name%
|
|
||||||
translation: /store/apple/%two_letters_code%/copy.resx
|
translation: /store/apple/%two_letters_code%/copy.resx
|
||||||
update_option: update_as_unapproved
|
|
||||||
languages_mapping:
|
languages_mapping:
|
||||||
two_letters_code:
|
two_letters_code:
|
||||||
zh-CN: zh-Hans
|
zh-CN: zh-Hans
|
||||||
zh-TW: zh-Hant
|
zh-TW: zh-Hant
|
||||||
pt-PT: pt-PT
|
pt-PT: pt-PT
|
||||||
pt-BR: pt-BR
|
pt-BR: pt-BR
|
||||||
en-GB: en-GB
|
|
||||||
en-IN: en-IN
|
|
||||||
- source: /store/google/en/copy.resx
|
- source: /store/google/en/copy.resx
|
||||||
dest: /store/google/en/%original_file_name%
|
|
||||||
translation: /store/google/%two_letters_code%/copy.resx
|
translation: /store/google/%two_letters_code%/copy.resx
|
||||||
update_option: update_as_unapproved
|
|
||||||
languages_mapping:
|
languages_mapping:
|
||||||
two_letters_code:
|
two_letters_code:
|
||||||
zh-CN: zh-Hans
|
zh-CN: zh-Hans
|
||||||
zh-TW: zh-Hant
|
zh-TW: zh-Hant
|
||||||
pt-BR: pt-BR
|
pt-BR: pt-BR
|
||||||
pt-PT: pt-PT
|
pt-PT: pt-PT
|
||||||
en-GB: en-GB
|
|
||||||
en-IN: en-IN
|
|
||||||
|
|||||||
816
package-lock.json
generated
816
package-lock.json
generated
@@ -1,816 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "bitwarden-mobile",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"lockfileVersion": 2,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"name": "bitwarden-mobile",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"devDependencies": {
|
|
||||||
"gh-pages": "^3.2.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/array-union": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"array-uniq": "^1.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/array-uniq": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
|
|
||||||
"integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/async": {
|
|
||||||
"version": "2.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
|
||||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"lodash": "^4.17.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/balanced-match": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/brace-expansion": {
|
|
||||||
"version": "1.1.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"balanced-match": "^1.0.0",
|
|
||||||
"concat-map": "0.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/commander": {
|
|
||||||
"version": "2.20.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/commondir": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/concat-map": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
|
||||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/email-addresses": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/escape-string-regexp": {
|
|
||||||
"version": "1.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
|
||||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/filename-reserved-regex": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/filenamify": {
|
|
||||||
"version": "4.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz",
|
|
||||||
"integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"filename-reserved-regex": "^2.0.0",
|
|
||||||
"strip-outer": "^1.0.1",
|
|
||||||
"trim-repeated": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/find-cache-dir": {
|
|
||||||
"version": "3.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
|
|
||||||
"integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"commondir": "^1.0.1",
|
|
||||||
"make-dir": "^3.0.2",
|
|
||||||
"pkg-dir": "^4.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/avajs/find-cache-dir?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/find-up": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"locate-path": "^5.0.0",
|
|
||||||
"path-exists": "^4.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fs-extra": {
|
|
||||||
"version": "8.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
|
||||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"graceful-fs": "^4.2.0",
|
|
||||||
"jsonfile": "^4.0.0",
|
|
||||||
"universalify": "^0.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6 <7 || >=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fs.realpath": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/gh-pages": {
|
|
||||||
"version": "3.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz",
|
|
||||||
"integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"async": "^2.6.1",
|
|
||||||
"commander": "^2.18.0",
|
|
||||||
"email-addresses": "^3.0.1",
|
|
||||||
"filenamify": "^4.3.0",
|
|
||||||
"find-cache-dir": "^3.3.1",
|
|
||||||
"fs-extra": "^8.1.0",
|
|
||||||
"globby": "^6.1.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"gh-pages": "bin/gh-pages.js",
|
|
||||||
"gh-pages-clean": "bin/gh-pages-clean.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/glob": {
|
|
||||||
"version": "7.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
|
||||||
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"fs.realpath": "^1.0.0",
|
|
||||||
"inflight": "^1.0.4",
|
|
||||||
"inherits": "2",
|
|
||||||
"minimatch": "^3.0.4",
|
|
||||||
"once": "^1.3.0",
|
|
||||||
"path-is-absolute": "^1.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/isaacs"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/globby": {
|
|
||||||
"version": "6.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
|
||||||
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"array-union": "^1.0.1",
|
|
||||||
"glob": "^7.0.3",
|
|
||||||
"object-assign": "^4.0.1",
|
|
||||||
"pify": "^2.0.0",
|
|
||||||
"pinkie-promise": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/graceful-fs": {
|
|
||||||
"version": "4.2.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
|
|
||||||
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/inflight": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
|
||||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"once": "^1.3.0",
|
|
||||||
"wrappy": "1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/inherits": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/jsonfile": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
|
||||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
|
||||||
"dev": true,
|
|
||||||
"optionalDependencies": {
|
|
||||||
"graceful-fs": "^4.1.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/locate-path": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"p-locate": "^4.1.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/lodash": {
|
|
||||||
"version": "4.17.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/make-dir": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"semver": "^6.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/minimatch": {
|
|
||||||
"version": "3.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
|
||||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"brace-expansion": "^1.1.7"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/object-assign": {
|
|
||||||
"version": "4.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
|
||||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/once": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
|
||||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"wrappy": "1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/p-limit": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
|
||||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"p-try": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/p-locate": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"p-limit": "^2.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/p-try": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/path-exists": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/path-is-absolute": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pify": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
|
||||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pinkie": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
|
|
||||||
"integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pinkie-promise": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"pinkie": "^2.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pkg-dir": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"find-up": "^4.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/semver": {
|
|
||||||
"version": "6.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
|
||||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/strip-outer": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"escape-string-regexp": "^1.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/trim-repeated": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"escape-string-regexp": "^1.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/universalify": {
|
|
||||||
"version": "0.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
|
||||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/wrappy": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"array-union": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"array-uniq": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"array-uniq": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
|
|
||||||
"integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"async": {
|
|
||||||
"version": "2.6.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
|
||||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"lodash": "^4.17.14"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"balanced-match": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"brace-expansion": {
|
|
||||||
"version": "1.1.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
|
||||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"balanced-match": "^1.0.0",
|
|
||||||
"concat-map": "0.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"commander": {
|
|
||||||
"version": "2.20.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
|
||||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"commondir": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"concat-map": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
|
||||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"email-addresses": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"escape-string-regexp": {
|
|
||||||
"version": "1.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
|
||||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"filename-reserved-regex": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"filenamify": {
|
|
||||||
"version": "4.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz",
|
|
||||||
"integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"filename-reserved-regex": "^2.0.0",
|
|
||||||
"strip-outer": "^1.0.1",
|
|
||||||
"trim-repeated": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"find-cache-dir": {
|
|
||||||
"version": "3.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
|
|
||||||
"integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"commondir": "^1.0.1",
|
|
||||||
"make-dir": "^3.0.2",
|
|
||||||
"pkg-dir": "^4.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"find-up": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"locate-path": "^5.0.0",
|
|
||||||
"path-exists": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fs-extra": {
|
|
||||||
"version": "8.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
|
|
||||||
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"graceful-fs": "^4.2.0",
|
|
||||||
"jsonfile": "^4.0.0",
|
|
||||||
"universalify": "^0.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fs.realpath": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"gh-pages": {
|
|
||||||
"version": "3.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz",
|
|
||||||
"integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"async": "^2.6.1",
|
|
||||||
"commander": "^2.18.0",
|
|
||||||
"email-addresses": "^3.0.1",
|
|
||||||
"filenamify": "^4.3.0",
|
|
||||||
"find-cache-dir": "^3.3.1",
|
|
||||||
"fs-extra": "^8.1.0",
|
|
||||||
"globby": "^6.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"glob": {
|
|
||||||
"version": "7.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
|
||||||
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"fs.realpath": "^1.0.0",
|
|
||||||
"inflight": "^1.0.4",
|
|
||||||
"inherits": "2",
|
|
||||||
"minimatch": "^3.0.4",
|
|
||||||
"once": "^1.3.0",
|
|
||||||
"path-is-absolute": "^1.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"globby": {
|
|
||||||
"version": "6.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
|
||||||
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"array-union": "^1.0.1",
|
|
||||||
"glob": "^7.0.3",
|
|
||||||
"object-assign": "^4.0.1",
|
|
||||||
"pify": "^2.0.0",
|
|
||||||
"pinkie-promise": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"graceful-fs": {
|
|
||||||
"version": "4.2.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz",
|
|
||||||
"integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"inflight": {
|
|
||||||
"version": "1.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
|
||||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"once": "^1.3.0",
|
|
||||||
"wrappy": "1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"inherits": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
|
||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"jsonfile": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
|
||||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"graceful-fs": "^4.1.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"locate-path": {
|
|
||||||
"version": "5.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
|
||||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"p-locate": "^4.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lodash": {
|
|
||||||
"version": "4.17.21",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"make-dir": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"semver": "^6.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"minimatch": {
|
|
||||||
"version": "3.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
|
||||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"brace-expansion": "^1.1.7"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"object-assign": {
|
|
||||||
"version": "4.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
|
||||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"once": {
|
|
||||||
"version": "1.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
|
||||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"wrappy": "1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-limit": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
|
||||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"p-try": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-locate": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"p-limit": "^2.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"p-try": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"path-exists": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"path-is-absolute": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"pify": {
|
|
||||||
"version": "2.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
|
||||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"pinkie": {
|
|
||||||
"version": "2.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
|
|
||||||
"integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"pinkie-promise": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
|
|
||||||
"integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"pinkie": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pkg-dir": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"find-up": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"semver": {
|
|
||||||
"version": "6.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
|
||||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"strip-outer": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
|
|
||||||
"integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"escape-string-regexp": "^1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"trim-repeated": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"escape-string-regexp": "^1.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"universalify": {
|
|
||||||
"version": "0.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
|
||||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"wrappy": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
|
||||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
package.json
11
package.json
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "bitwarden-mobile",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"scripts": {
|
|
||||||
"deploy": "gh-pages --dist dist",
|
|
||||||
"clean:l10n": "git push origin --delete l10n_master"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"gh-pages": "^3.2.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
@@ -1,906 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Graphics;
|
|
||||||
using Android.OS;
|
|
||||||
using Android.Provider;
|
|
||||||
using Android.Runtime;
|
|
||||||
using Android.Views;
|
|
||||||
using Android.Views.Accessibility;
|
|
||||||
using Android.Widget;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.Core;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Accessibility
|
|
||||||
{
|
|
||||||
public static class AccessibilityHelpers
|
|
||||||
{
|
|
||||||
public static Credentials LastCredentials = null;
|
|
||||||
public static string SystemUiPackage = "com.android.systemui";
|
|
||||||
public static string BitwardenTag = "bw_access";
|
|
||||||
public static bool IsAutofillTileAdded = false;
|
|
||||||
public static bool IsAccessibilityBroadcastReady = false;
|
|
||||||
|
|
||||||
// Be sure to keep these two sections sorted alphabetically
|
|
||||||
public static Dictionary<string, Browser> SupportedBrowsers => new List<Browser>
|
|
||||||
{
|
|
||||||
// [Section A] Entries also present in the list of Autofill Framework
|
|
||||||
//
|
|
||||||
// So keep them in sync with:
|
|
||||||
// - AutofillHelpers.{TrustedBrowsers,CompatBrowsers}
|
|
||||||
// - Resources/xml/autofillservice.xml
|
|
||||||
new Browser("com.amazon.cloud9", "url"),
|
|
||||||
new Browser("com.android.browser", "url"),
|
|
||||||
new Browser("com.android.chrome", "url_bar"),
|
|
||||||
// Rem. for "com.android.htmlviewer": doesn't have a URL bar, therefore not present here.
|
|
||||||
new Browser("com.avast.android.secure.browser", "editor"),
|
|
||||||
new Browser("com.avg.android.secure.browser", "editor"),
|
|
||||||
new Browser("com.brave.browser", "url_bar"),
|
|
||||||
new Browser("com.brave.browser_beta", "url_bar"),
|
|
||||||
new Browser("com.brave.browser_default", "url_bar"),
|
|
||||||
new Browser("com.brave.browser_dev", "url_bar"),
|
|
||||||
new Browser("com.brave.browser_nightly", "url_bar"),
|
|
||||||
new Browser("com.chrome.beta", "url_bar"),
|
|
||||||
new Browser("com.chrome.canary", "url_bar"),
|
|
||||||
new Browser("com.chrome.dev", "url_bar"),
|
|
||||||
new Browser("com.cookiegames.smartcookie", "search"),
|
|
||||||
new Browser("com.cookiejarapps.android.smartcookieweb", "mozac_browser_toolbar_url_view"),
|
|
||||||
new Browser("com.duckduckgo.mobile.android", "omnibarTextInput"),
|
|
||||||
new Browser("com.ecosia.android", "url_bar"),
|
|
||||||
new Browser("com.google.android.apps.chrome", "url_bar"),
|
|
||||||
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
|
||||||
// Rem. for "com.google.android.captiveportallogin": URL displayed in ActionBar subtitle without viewId.
|
|
||||||
new Browser("com.jamal2367.styx", "search"),
|
|
||||||
new Browser("com.kiwibrowser.browser", "url_bar"),
|
|
||||||
new Browser("com.microsoft.emmx", "url_bar"),
|
|
||||||
new Browser("com.microsoft.emmx.beta", "url_bar"),
|
|
||||||
new Browser("com.microsoft.emmx.canary", "url_bar"),
|
|
||||||
new Browser("com.microsoft.emmx.dev", "url_bar"),
|
|
||||||
new Browser("com.mmbox.browser", "search_box"),
|
|
||||||
new Browser("com.mmbox.xbrowser", "search_box"),
|
|
||||||
new Browser("com.mycompany.app.soulbrowser", "edit_text"),
|
|
||||||
new Browser("com.naver.whale", "url_bar"),
|
|
||||||
new Browser("com.opera.browser", "url_field"),
|
|
||||||
new Browser("com.opera.browser.beta", "url_field"),
|
|
||||||
new Browser("com.opera.mini.native", "url_field"),
|
|
||||||
new Browser("com.opera.mini.native.beta", "url_field"),
|
|
||||||
new Browser("com.opera.touch", "addressbarEdit"),
|
|
||||||
new Browser("com.qflair.browserq", "url"),
|
|
||||||
new Browser("com.qwant.liberty", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v4)
|
|
||||||
new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"),
|
|
||||||
new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"),
|
|
||||||
new Browser("com.stoutner.privacybrowser.free", "url_edittext"),
|
|
||||||
new Browser("com.stoutner.privacybrowser.standard", "url_edittext"),
|
|
||||||
new Browser("com.vivaldi.browser", "url_bar"),
|
|
||||||
new Browser("com.vivaldi.browser.snapshot", "url_bar"),
|
|
||||||
new Browser("com.vivaldi.browser.sopranos", "url_bar"),
|
|
||||||
new Browser("com.yandex.browser", "bro_omnibar_address_title_text,bro_omnibox_collapsed_title",
|
|
||||||
(s) => s.Split(new char[]{' ', ' '}).FirstOrDefault()), // 0 = Regular Space, 1 = No-break space (00A0)
|
|
||||||
new Browser("com.z28j.feel", "g2"),
|
|
||||||
new Browser("idm.internet.download.manager", "search"),
|
|
||||||
new Browser("idm.internet.download.manager.adm.lite", "search"),
|
|
||||||
new Browser("idm.internet.download.manager.plus", "search"),
|
|
||||||
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
|
|
||||||
new Browser("mark.via", "am,an"),
|
|
||||||
new Browser("mark.via.gp", "as"),
|
|
||||||
new Browser("net.slions.fulguris.full.download", "search"),
|
|
||||||
new Browser("net.slions.fulguris.full.download.debug", "search"),
|
|
||||||
new Browser("net.slions.fulguris.full.playstore", "search"),
|
|
||||||
new Browser("net.slions.fulguris.full.playstore.debug", "search"),
|
|
||||||
new Browser("org.adblockplus.browser", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
|
|
||||||
new Browser("org.adblockplus.browser.beta", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
|
|
||||||
new Browser("org.bromite.bromite", "url_bar"),
|
|
||||||
new Browser("org.bromite.chromium", "url_bar"),
|
|
||||||
new Browser("org.chromium.chrome", "url_bar"),
|
|
||||||
new Browser("org.codeaurora.swe.browser", "url_bar"),
|
|
||||||
new Browser("org.gnu.icecat", "url_bar_title,mozac_browser_toolbar_url_view"), // 2nd = Anticipation
|
|
||||||
new Browser("org.mozilla.fenix", "mozac_browser_toolbar_url_view"),
|
|
||||||
new Browser("org.mozilla.fenix.nightly", "mozac_browser_toolbar_url_view"), // [DEPRECATED ENTRY]
|
|
||||||
new Browser("org.mozilla.fennec_aurora", "mozac_browser_toolbar_url_view,url_bar_title"), // [DEPRECATED ENTRY]
|
|
||||||
new Browser("org.mozilla.fennec_fdroid", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
|
|
||||||
new Browser("org.mozilla.firefox", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
|
|
||||||
new Browser("org.mozilla.firefox_beta", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
|
|
||||||
new Browser("org.mozilla.focus", "mozac_browser_toolbar_url_view,display_url"), // 2nd = Legacy
|
|
||||||
new Browser("org.mozilla.focus.beta", "mozac_browser_toolbar_url_view,display_url"), // 2nd = Legacy
|
|
||||||
new Browser("org.mozilla.focus.nightly", "mozac_browser_toolbar_url_view,display_url"), // 2nd = Legacy
|
|
||||||
new Browser("org.mozilla.klar", "mozac_browser_toolbar_url_view,display_url"), // 2nd = Legacy
|
|
||||||
new Browser("org.mozilla.reference.browser", "mozac_browser_toolbar_url_view"),
|
|
||||||
new Browser("org.mozilla.rocket", "display_url"),
|
|
||||||
new Browser("org.torproject.torbrowser", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v10.0.3)
|
|
||||||
new Browser("org.torproject.torbrowser_alpha", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v10.0a8)
|
|
||||||
new Browser("org.ungoogled.chromium.extensions.stable", "url_bar"),
|
|
||||||
new Browser("org.ungoogled.chromium.stable", "url_bar"),
|
|
||||||
new Browser("us.spotco.fennec_dos", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
|
|
||||||
|
|
||||||
// [Section B] Entries only present here
|
|
||||||
//
|
|
||||||
// FIXME: Test the compatibility of these with Autofill Framework
|
|
||||||
new Browser("acr.browser.barebones", "search"),
|
|
||||||
new Browser("acr.browser.lightning", "search"),
|
|
||||||
new Browser("com.feedback.browser.wjbrowser", "addressbar_url"),
|
|
||||||
new Browser("com.ghostery.android.ghostery", "search_field"),
|
|
||||||
new Browser("com.htc.sense.browser", "title"),
|
|
||||||
new Browser("com.jerky.browser2", "enterUrl"),
|
|
||||||
new Browser("com.ksmobile.cb", "address_bar_edit_text"),
|
|
||||||
new Browser("com.linkbubble.playstore", "url_text"),
|
|
||||||
new Browser("com.mx.browser", "address_editor_with_progress"),
|
|
||||||
new Browser("com.mx.browser.tablet", "address_editor_with_progress"),
|
|
||||||
new Browser("com.nubelacorp.javelin", "enterUrl"),
|
|
||||||
new Browser("jp.co.fenrir.android.sleipnir", "url_text"),
|
|
||||||
new Browser("jp.co.fenrir.android.sleipnir_black", "url_text"),
|
|
||||||
new Browser("jp.co.fenrir.android.sleipnir_test", "url_text"),
|
|
||||||
new Browser("mobi.mgeek.TunnyBrowser", "title"),
|
|
||||||
new Browser("org.iron.srware", "url_bar"),
|
|
||||||
}.ToDictionary(n => n.PackageName);
|
|
||||||
|
|
||||||
// Known packages to skip
|
|
||||||
public static HashSet<string> FilteredPackageNames => new HashSet<string>
|
|
||||||
{
|
|
||||||
SystemUiPackage,
|
|
||||||
"com.google.android.googlequicksearchbox",
|
|
||||||
"com.google.android.apps.nexuslauncher",
|
|
||||||
"com.google.android.launcher",
|
|
||||||
"com.computer.desktop.ui.launcher",
|
|
||||||
"com.launcher.notelauncher",
|
|
||||||
"com.anddoes.launcher",
|
|
||||||
"com.actionlauncher.playstore",
|
|
||||||
"ch.deletescape.lawnchair.plah",
|
|
||||||
"com.microsoft.launcher",
|
|
||||||
"com.teslacoilsw.launcher",
|
|
||||||
"com.teslacoilsw.launcher.prime",
|
|
||||||
"is.shortcut",
|
|
||||||
"me.craftsapp.nlauncher",
|
|
||||||
"com.ss.squarehome2",
|
|
||||||
"com.treydev.pns"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Be sure to keep these sections sorted alphabetically
|
|
||||||
public static Dictionary<string, KnownUsernameField> KnownUsernameFields => new List<KnownUsernameField>
|
|
||||||
{
|
|
||||||
/**************************************************************************************
|
|
||||||
* SECTION A ——— World-renowned web sites/applications
|
|
||||||
*************************************************************************************/
|
|
||||||
|
|
||||||
// REM.: For this type of web sites/applications, the Top 100 (SimilarWeb, 2019)
|
|
||||||
// and the Top 50 (Alexa Internet, 2020) are covered. National variants
|
|
||||||
// have been added when available. Mobile and desktop versions supported.
|
|
||||||
//
|
|
||||||
// A few other popular web sites/applications have also been added.
|
|
||||||
//
|
|
||||||
// Could not be added, however:
|
|
||||||
// web sites/applications that don't use an "id" attribute for their login field.
|
|
||||||
|
|
||||||
// NOTE: The case of OAuth compatible web sites/applications that also provide
|
|
||||||
// a "user ID only" login page in this situation
|
|
||||||
// was taken into account in the tests as well.
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Amazon ——— ap_email_login = mobile / ap_email = desktop (amazon.co.jp currently uses ap_email in both cases, as of July 2020).
|
|
||||||
new KnownUsernameField("amazon.ae", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.ca", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.cn", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.co.jp", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.co.uk", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.com", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.com.au", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.com.br", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.com.mx", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.com.tr", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.de", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.es", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.fr", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.in", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.it", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.nl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.pl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.sa", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.se", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
new KnownUsernameField("amazon.sg", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
|
|
||||||
|
|
||||||
// Amazon Web Services
|
|
||||||
new KnownUsernameField("signin.aws.amazon.com", new (string, string)[] { ("signin", "resolving_input") }),
|
|
||||||
|
|
||||||
// Atlassian
|
|
||||||
new KnownUsernameField("id.atlassian.com", new (string, string)[] { ("login", "username") }),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* B
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Bitly ——— enterprise users.
|
|
||||||
new KnownUsernameField("bitly.com", new (string, string)[] { ("/sso/url_slug", "url_slug") }),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* E
|
|
||||||
*/
|
|
||||||
|
|
||||||
// eBay ——— 1st = traditional access / 2nd = direct access (i.e. https://signin.ebay.tld/).
|
|
||||||
new KnownUsernameField("signin.befr.ebay.be", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.benl.ebay.be", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.cafr.ebay.ca", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.at", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.be", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.ca", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.ch", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.co.uk", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.com", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.com.au", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.com.hk", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.com.my", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.com.sg", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.de", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.es", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.fr", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.ie", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.in", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.it", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.nl", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.ph", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
new KnownUsernameField("signin.ebay.pl", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* G
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Google ——— 1st = used in most cases (v2) / 2nd = used in some cases (v1).
|
|
||||||
new KnownUsernameField("accounts.google.com", new (string, string)[] { ("identifier", "identifierId"), ("ServiceLogin", "Email") }),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* P
|
|
||||||
*/
|
|
||||||
|
|
||||||
// PayPal ——— 1st = traditional access / 2nd = access using OAuth.
|
|
||||||
new KnownUsernameField("paypal.com", new (string, string)[] { ("signin", "email"), ("contains:/connect/", "email") }),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* T
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Tumblr ——— despite "signup" in its ID, it's the login field (the website offers registration if the account doesn't exist).
|
|
||||||
new KnownUsernameField("tumblr.com", new (string, string)[] { ("login", "signup_determine_email") }),
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Y
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Yandex
|
|
||||||
new KnownUsernameField("passport.yandex.az", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.by", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.co.il", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.com", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.com.am", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.com.ge", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.com.tr", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.ee", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.fi", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.fr", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.kg", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.kz", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.lt", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.lv", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.md", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.pl", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.ru", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.tj", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.tm", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.ua", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
new KnownUsernameField("passport.yandex.uz", new (string, string)[] { ("auth", "passp-field-login") }),
|
|
||||||
|
|
||||||
/**************************************************************************************
|
|
||||||
* SECTION B ——— Top 100 worldwide
|
|
||||||
*************************************************************************************/
|
|
||||||
|
|
||||||
// As of July 2020, all entries that needed to be added from
|
|
||||||
// Top 100 (SimilarWeb, 2019) and Top 50 (Alexa Internet, 2020)
|
|
||||||
// matched section A.
|
|
||||||
//
|
|
||||||
// Therefore, no entry currently.
|
|
||||||
|
|
||||||
/**************************************************************************************
|
|
||||||
* SECTION C ——— Top 20 for selected countries
|
|
||||||
*************************************************************************************/
|
|
||||||
|
|
||||||
// REM.: For these selected countries, the Top 20 (SimilarWeb, 2020)
|
|
||||||
// and the Top 20 (Alexa Internet, 2020) are covered.
|
|
||||||
// Mobile and desktop versions supported.
|
|
||||||
//
|
|
||||||
// Could not be added, however:
|
|
||||||
// web sites/applications that don't use an "id" attribute for their login field.
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Japan
|
|
||||||
*/
|
|
||||||
|
|
||||||
// NTT DOCOMO ——— mainly used for "My docomo".
|
|
||||||
new KnownUsernameField("cfg.smt.docomo.ne.jp", new (string, string)[] { ("contains:/auth/", "Di_Uid") }),
|
|
||||||
new KnownUsernameField("id.smt.docomo.ne.jp", new (string, string)[] { ("contains:/cgi7/", "Di_Uid") }),
|
|
||||||
|
|
||||||
/**************************************************************************************
|
|
||||||
* SECTION D ——— Miscellaneous
|
|
||||||
*************************************************************************************/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Various entries ——— Following user requests, etc.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// No entry, currently.
|
|
||||||
|
|
||||||
/**************************************************************************************
|
|
||||||
* SECTION Z ——— Special forms
|
|
||||||
*
|
|
||||||
* Despite "user ID + password" fields both visible, detection rules required.
|
|
||||||
*************************************************************************************/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Main
|
|
||||||
*/
|
|
||||||
|
|
||||||
// No entry, currently.
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Test/example purposes only
|
|
||||||
*/
|
|
||||||
|
|
||||||
// GitHub ——— VERY special case (signup form, just to test the proper functioning of special forms).
|
|
||||||
new KnownUsernameField("github.com", new (string, string)[] { ("", "user[login]-footer") }),
|
|
||||||
}.ToDictionary(n => n.UriAuthority);
|
|
||||||
|
|
||||||
public static void PrintTestData(AccessibilityNodeInfo root, AccessibilityEvent e)
|
|
||||||
{
|
|
||||||
var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
|
|
||||||
var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
|
|
||||||
foreach (var node in testNodesData)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine("Node: {0} = {1}", node.id, node.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetUri(AccessibilityNodeInfo root)
|
|
||||||
{
|
|
||||||
var uri = string.Concat(Constants.AndroidAppProtocol, root.PackageName);
|
|
||||||
if (SupportedBrowsers.ContainsKey(root.PackageName))
|
|
||||||
{
|
|
||||||
var browser = SupportedBrowsers[root.PackageName];
|
|
||||||
AccessibilityNodeInfo addressNode = null;
|
|
||||||
foreach (var uriViewId in browser.UriViewId.Split(","))
|
|
||||||
{
|
|
||||||
addressNode = root.FindAccessibilityNodeInfosByViewId(
|
|
||||||
$"{root.PackageName}:id/{uriViewId}").FirstOrDefault();
|
|
||||||
if (addressNode != null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addressNode != null)
|
|
||||||
{
|
|
||||||
uri = ExtractUri(uri, addressNode, browser);
|
|
||||||
addressNode.Recycle();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Return null to prevent overwriting notification pendingIntent uri with browser packageName
|
|
||||||
// (we login to pages, not browsers)
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser)
|
|
||||||
{
|
|
||||||
if (addressNode?.Text == null)
|
|
||||||
{
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
if (addressNode.Text == null)
|
|
||||||
{
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
uri = browser.GetUriFunction(addressNode.Text)?.Trim();
|
|
||||||
if (uri != null && uri.Contains("."))
|
|
||||||
{
|
|
||||||
var hasHttpProtocol = uri.StartsWith("http://") || uri.StartsWith("https://");
|
|
||||||
if (!hasHttpProtocol && uri.Contains("."))
|
|
||||||
{
|
|
||||||
if (Uri.TryCreate("https://" + uri, UriKind.Absolute, out var _))
|
|
||||||
{
|
|
||||||
return string.Concat("https://", uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Uri.TryCreate(uri, UriKind.Absolute, out var _))
|
|
||||||
{
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Check to make sure it is ok to autofill still on the current screen
|
|
||||||
/// </summary>
|
|
||||||
public static bool NeedToAutofill(Credentials credentials, string currentUriString)
|
|
||||||
{
|
|
||||||
if (credentials == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (Uri.TryCreate(credentials.LastUri, UriKind.Absolute, out Uri lastUri) &&
|
|
||||||
Uri.TryCreate(currentUriString, UriKind.Absolute, out Uri currentUri))
|
|
||||||
{
|
|
||||||
return lastUri.Host == currentUri.Host;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool EditText(AccessibilityNodeInfo n)
|
|
||||||
{
|
|
||||||
return n?.ClassName?.Contains("EditText") ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void FillCredentials(AccessibilityNodeInfo usernameNode,
|
|
||||||
IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
|
||||||
{
|
|
||||||
FillEditText(usernameNode, LastCredentials?.Username);
|
|
||||||
foreach (var n in passwordNodes)
|
|
||||||
{
|
|
||||||
FillEditText(n, LastCredentials?.Password);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void FillEditText(AccessibilityNodeInfo editTextNode, string value)
|
|
||||||
{
|
|
||||||
if (editTextNode == null || value == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var bundle = new Bundle();
|
|
||||||
bundle.PutString(AccessibilityNodeInfo.ActionArgumentSetTextCharsequence, value);
|
|
||||||
editTextNode.PerformAction(Android.Views.Accessibility.Action.SetText, bundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NodeList GetWindowNodes(AccessibilityNodeInfo n, AccessibilityEvent e,
|
|
||||||
Func<AccessibilityNodeInfo, bool> condition, bool disposeIfUnused, NodeList nodes = null,
|
|
||||||
int recursionDepth = 0)
|
|
||||||
{
|
|
||||||
if (nodes == null)
|
|
||||||
{
|
|
||||||
nodes = new NodeList();
|
|
||||||
}
|
|
||||||
var dispose = disposeIfUnused;
|
|
||||||
if (n != null && recursionDepth < 100)
|
|
||||||
{
|
|
||||||
var add = n.WindowId == e.WindowId &&
|
|
||||||
!(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) &&
|
|
||||||
condition(n);
|
|
||||||
if (add)
|
|
||||||
{
|
|
||||||
dispose = false;
|
|
||||||
nodes.Add(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < n.ChildCount; i++)
|
|
||||||
{
|
|
||||||
var childNode = n.GetChild(i);
|
|
||||||
if (childNode == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (i > 100)
|
|
||||||
{
|
|
||||||
Android.Util.Log.Info(BitwardenTag, "Too many child iterations.");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (childNode.GetHashCode() == n.GetHashCode())
|
|
||||||
{
|
|
||||||
Android.Util.Log.Info(BitwardenTag, "Child node is the same as parent for some reason.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GetWindowNodes(childNode, e, condition, true, nodes, recursionDepth++);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dispose)
|
|
||||||
{
|
|
||||||
n?.Recycle();
|
|
||||||
n?.Dispose();
|
|
||||||
}
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AccessibilityNodeInfo GetUsernameEditText(string uriString,
|
|
||||||
IEnumerable<AccessibilityNodeInfo> allEditTexts)
|
|
||||||
{
|
|
||||||
string uriAuthority = null;
|
|
||||||
string uriKey = null;
|
|
||||||
string uriLocalPath = null;
|
|
||||||
if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri))
|
|
||||||
{
|
|
||||||
uriAuthority = uri.Authority;
|
|
||||||
uriKey = uriAuthority.StartsWith("www.", StringComparison.Ordinal) ? uriAuthority.Substring(4) : uriAuthority;
|
|
||||||
uriLocalPath = uri.LocalPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(uriKey))
|
|
||||||
{
|
|
||||||
// Uncomment this to log values necessary for username field discovery
|
|
||||||
// foreach (var editText in allEditTexts)
|
|
||||||
// {
|
|
||||||
// System.Diagnostics.Debug.WriteLine(">>> uriKey: {0}, uriLocalPath: {1}, viewId: {2}", uriKey,
|
|
||||||
// uriLocalPath, editText.ViewIdResourceName);
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (KnownUsernameFields.ContainsKey(uriKey))
|
|
||||||
{
|
|
||||||
var usernameField = KnownUsernameFields[uriKey];
|
|
||||||
(string UriPathWanted, string UsernameViewId)[] accessOptions = usernameField.AccessOptions;
|
|
||||||
|
|
||||||
for (int i = 0; i < accessOptions.Length; i++)
|
|
||||||
{
|
|
||||||
string curUriPathWanted = accessOptions[i].UriPathWanted;
|
|
||||||
string curUsernameViewId = accessOptions[i].UsernameViewId;
|
|
||||||
bool uriLocalPathMatches = false;
|
|
||||||
|
|
||||||
// Case-sensitive comparison
|
|
||||||
if (curUriPathWanted.StartsWith("startswith:", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
curUriPathWanted = curUriPathWanted.Substring(11);
|
|
||||||
uriLocalPathMatches = uriLocalPath.StartsWith(curUriPathWanted, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
else if (curUriPathWanted.StartsWith("contains:", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
curUriPathWanted = curUriPathWanted.Substring(9);
|
|
||||||
uriLocalPathMatches = uriLocalPath.Contains(curUriPathWanted, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
else if (curUriPathWanted.StartsWith("endswith:", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
curUriPathWanted = curUriPathWanted.Substring(9);
|
|
||||||
uriLocalPathMatches = uriLocalPath.EndsWith(curUriPathWanted, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case-insensitive comparison
|
|
||||||
else if (curUriPathWanted.StartsWith("istartswith:", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
curUriPathWanted = curUriPathWanted.Substring(12);
|
|
||||||
uriLocalPathMatches = uriLocalPath.StartsWith(curUriPathWanted, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
else if (curUriPathWanted.StartsWith("icontains:", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
curUriPathWanted = curUriPathWanted.Substring(10);
|
|
||||||
uriLocalPathMatches = uriLocalPath.Contains(curUriPathWanted, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
else if (curUriPathWanted.StartsWith("iendswith:", StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
curUriPathWanted = curUriPathWanted.Substring(10);
|
|
||||||
uriLocalPathMatches = uriLocalPath.EndsWith(curUriPathWanted, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default type of comparison
|
|
||||||
else
|
|
||||||
{
|
|
||||||
uriLocalPathMatches = uriLocalPath.EndsWith(curUriPathWanted, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uriLocalPathMatches)
|
|
||||||
{
|
|
||||||
foreach (var editText in allEditTexts)
|
|
||||||
{
|
|
||||||
foreach (var usernameViewId in curUsernameViewId.Split(","))
|
|
||||||
{
|
|
||||||
if (usernameViewId == editText.ViewIdResourceName)
|
|
||||||
{
|
|
||||||
return editText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no match found, attempt to establish username field based on password field
|
|
||||||
return GetUsernameEditTextIfPasswordExists(allEditTexts);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AccessibilityNodeInfo GetUsernameEditTextIfPasswordExists(
|
|
||||||
IEnumerable<AccessibilityNodeInfo> allEditTexts)
|
|
||||||
{
|
|
||||||
AccessibilityNodeInfo previousEditText = null;
|
|
||||||
foreach (var editText in allEditTexts)
|
|
||||||
{
|
|
||||||
if (editText.Password)
|
|
||||||
{
|
|
||||||
return previousEditText;
|
|
||||||
}
|
|
||||||
previousEditText = editText;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsUsernameEditText(AccessibilityNodeInfo root, AccessibilityEvent e)
|
|
||||||
{
|
|
||||||
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
|
|
||||||
var uriString = GetUri(root);
|
|
||||||
var usernameEditText = GetUsernameEditText(uriString, allEditTexts);
|
|
||||||
|
|
||||||
var isUsernameEditText = false;
|
|
||||||
if (usernameEditText != null)
|
|
||||||
{
|
|
||||||
isUsernameEditText = IsSameNode(usernameEditText, e.Source);
|
|
||||||
}
|
|
||||||
allEditTexts.Dispose();
|
|
||||||
|
|
||||||
return isUsernameEditText;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsSameNode(AccessibilityNodeInfo node1, AccessibilityNodeInfo node2)
|
|
||||||
{
|
|
||||||
if (node1 != null && node2 != null)
|
|
||||||
{
|
|
||||||
return node1.Equals(node2) || node1.GetHashCode() == node2.GetHashCode();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool OverlayPermitted()
|
|
||||||
{
|
|
||||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
|
||||||
{
|
|
||||||
if (Settings.CanDrawOverlays(Application.Context))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var appOpsMgr = (AppOpsManager)Application.Context.GetSystemService(Context.AppOpsService);
|
|
||||||
var mode = appOpsMgr.CheckOpNoThrow("android:system_alert_window", Process.MyUid(),
|
|
||||||
Application.Context.PackageName);
|
|
||||||
if (mode == AppOpsManagerMode.Allowed || mode == AppOpsManagerMode.Ignored)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var wm = Application.Context.GetSystemService(Context.WindowService)
|
|
||||||
.JavaCast<IWindowManager>();
|
|
||||||
if (wm == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var testView = new View(Application.Context);
|
|
||||||
var layoutParams = GetOverlayLayoutParams();
|
|
||||||
wm.AddView(testView, layoutParams);
|
|
||||||
wm.RemoveView(testView);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// older android versions are always true
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LinearLayout GetOverlayView(Context context)
|
|
||||||
{
|
|
||||||
var inflater = (LayoutInflater)context.GetSystemService(Context.LayoutInflaterService);
|
|
||||||
var view = (LinearLayout)inflater.Inflate(Resource.Layout.autofill_listitem, null);
|
|
||||||
var text1 = (TextView)view.FindViewById(Resource.Id.text1);
|
|
||||||
var text2 = (TextView)view.FindViewById(Resource.Id.text2);
|
|
||||||
var icon = (ImageView)view.FindViewById(Resource.Id.icon);
|
|
||||||
text1.Text = AppResources.AutofillWithBitwarden;
|
|
||||||
text2.Text = AppResources.GoToMyVault;
|
|
||||||
icon.SetImageResource(Resource.Drawable.shield);
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WindowManagerLayoutParams GetOverlayLayoutParams()
|
|
||||||
{
|
|
||||||
WindowManagerTypes windowManagerType;
|
|
||||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
|
||||||
{
|
|
||||||
windowManagerType = WindowManagerTypes.ApplicationOverlay;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
windowManagerType = WindowManagerTypes.Phone;
|
|
||||||
}
|
|
||||||
|
|
||||||
var layoutParams = new WindowManagerLayoutParams(
|
|
||||||
ViewGroup.LayoutParams.WrapContent,
|
|
||||||
ViewGroup.LayoutParams.WrapContent,
|
|
||||||
windowManagerType,
|
|
||||||
WindowManagerFlags.NotFocusable | WindowManagerFlags.NotTouchModal,
|
|
||||||
Format.Transparent);
|
|
||||||
layoutParams.Gravity = GravityFlags.Top | GravityFlags.Left;
|
|
||||||
|
|
||||||
return layoutParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Point GetOverlayAnchorPosition(AccessibilityService service, AccessibilityNodeInfo anchorView,
|
|
||||||
int overlayViewHeight, bool isOverlayAboveAnchor)
|
|
||||||
{
|
|
||||||
var anchorViewRect = new Rect();
|
|
||||||
anchorView.GetBoundsInScreen(anchorViewRect);
|
|
||||||
var anchorViewX = anchorViewRect.Left;
|
|
||||||
var anchorViewY = isOverlayAboveAnchor ? anchorViewRect.Top : anchorViewRect.Bottom;
|
|
||||||
anchorViewRect.Dispose();
|
|
||||||
|
|
||||||
if (isOverlayAboveAnchor)
|
|
||||||
{
|
|
||||||
anchorViewY -= overlayViewHeight;
|
|
||||||
}
|
|
||||||
anchorViewY -= GetStatusBarHeight(service);
|
|
||||||
|
|
||||||
return new Point(anchorViewX, anchorViewY);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Point GetOverlayAnchorPosition(AccessibilityService service, AccessibilityNodeInfo anchorNode,
|
|
||||||
AccessibilityNodeInfo root, IEnumerable<AccessibilityWindowInfo> windows, int overlayViewHeight,
|
|
||||||
bool isOverlayAboveAnchor)
|
|
||||||
{
|
|
||||||
Point point = null;
|
|
||||||
if (anchorNode != null)
|
|
||||||
{
|
|
||||||
// Update node's info since this is still a reference from an older event
|
|
||||||
anchorNode.Refresh();
|
|
||||||
if (!anchorNode.VisibleToUser)
|
|
||||||
{
|
|
||||||
return new Point(-1, -1);
|
|
||||||
}
|
|
||||||
if (!anchorNode.Focused)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// node.VisibleToUser doesn't always give us exactly what we want, so attempt to tighten up the range
|
|
||||||
// of visibility
|
|
||||||
var inputMethodHeight = 0;
|
|
||||||
if (windows != null)
|
|
||||||
{
|
|
||||||
if (IsStatusBarExpanded(windows))
|
|
||||||
{
|
|
||||||
return new Point(-1, -1);
|
|
||||||
}
|
|
||||||
inputMethodHeight = GetInputMethodHeight(windows);
|
|
||||||
}
|
|
||||||
var minY = 0;
|
|
||||||
var rootNodeHeight = GetNodeHeight(root);
|
|
||||||
if (rootNodeHeight == -1)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var maxY = rootNodeHeight - GetNavigationBarHeight(service) - GetStatusBarHeight(service) -
|
|
||||||
inputMethodHeight;
|
|
||||||
|
|
||||||
point = GetOverlayAnchorPosition(service, anchorNode, overlayViewHeight, isOverlayAboveAnchor);
|
|
||||||
if (point.Y < minY)
|
|
||||||
{
|
|
||||||
if (isOverlayAboveAnchor)
|
|
||||||
{
|
|
||||||
// view nearing bounds, anchor to bottom
|
|
||||||
point.X = -1;
|
|
||||||
point.Y = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// view out of bounds, hide overlay
|
|
||||||
point.X = -1;
|
|
||||||
point.Y = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (point.Y > (maxY - overlayViewHeight))
|
|
||||||
{
|
|
||||||
if (isOverlayAboveAnchor)
|
|
||||||
{
|
|
||||||
// view out of bounds, hide overlay
|
|
||||||
point.X = -1;
|
|
||||||
point.Y = -1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// view nearing bounds, anchor to top
|
|
||||||
point.X = 0;
|
|
||||||
point.Y = -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (isOverlayAboveAnchor && point.Y < (maxY - (overlayViewHeight * 2) - GetNodeHeight(anchorNode)))
|
|
||||||
{
|
|
||||||
// This else block forces the overlay to return to bottom alignment as soon as space is available
|
|
||||||
// below the anchor view. Removing this will change the behavior to wait until there isn't enough
|
|
||||||
// space above the anchor view before returning to bottom alignment.
|
|
||||||
point.X = -1;
|
|
||||||
point.Y = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return point;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsStatusBarExpanded(IEnumerable<AccessibilityWindowInfo> windows)
|
|
||||||
{
|
|
||||||
if (windows != null && windows.Any())
|
|
||||||
{
|
|
||||||
var isSystemWindowsOnly = true;
|
|
||||||
foreach (var window in windows)
|
|
||||||
{
|
|
||||||
if (window.Type != AccessibilityWindowType.System)
|
|
||||||
{
|
|
||||||
isSystemWindowsOnly = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isSystemWindowsOnly;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int GetInputMethodHeight(IEnumerable<AccessibilityWindowInfo> windows)
|
|
||||||
{
|
|
||||||
var inputMethodWindowHeight = 0;
|
|
||||||
if (windows != null)
|
|
||||||
{
|
|
||||||
foreach (var window in windows)
|
|
||||||
{
|
|
||||||
if (window.Type == AccessibilityWindowType.InputMethod)
|
|
||||||
{
|
|
||||||
var windowRect = new Rect();
|
|
||||||
window.GetBoundsInScreen(windowRect);
|
|
||||||
inputMethodWindowHeight = windowRect.Height();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return inputMethodWindowHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsAutofillServicePromptVisible(IEnumerable<AccessibilityWindowInfo> windows)
|
|
||||||
{
|
|
||||||
// Autofill framework not available until API 26
|
|
||||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
|
||||||
{
|
|
||||||
return windows?.Any(w => w.Title?.ToLower().Contains("autofill") ?? false) ?? false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int GetNodeHeight(AccessibilityNodeInfo node)
|
|
||||||
{
|
|
||||||
if (node == null)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
var nodeRect = new Rect();
|
|
||||||
node.GetBoundsInScreen(nodeRect);
|
|
||||||
var nodeRectHeight = nodeRect.Height();
|
|
||||||
nodeRect.Dispose();
|
|
||||||
return nodeRectHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetStatusBarHeight(AccessibilityService service)
|
|
||||||
{
|
|
||||||
return GetSystemResourceDimenPx(service, "status_bar_height");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetNavigationBarHeight(AccessibilityService service)
|
|
||||||
{
|
|
||||||
return GetSystemResourceDimenPx(service, "navigation_bar_height");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int GetSystemResourceDimenPx(AccessibilityService service, string resName)
|
|
||||||
{
|
|
||||||
var resourceId = service.Resources.GetIdentifier(resName, "dimen", "android");
|
|
||||||
if (resourceId > 0)
|
|
||||||
{
|
|
||||||
return service.Resources.GetDimensionPixelSize(resourceId);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,473 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.OS;
|
|
||||||
using Android.Runtime;
|
|
||||||
using Android.Views;
|
|
||||||
using Android.Views.Accessibility;
|
|
||||||
using Android.Widget;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Accessibility
|
|
||||||
{
|
|
||||||
[Service(Permission = Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden")]
|
|
||||||
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
|
|
||||||
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
|
|
||||||
[Register("com.x8bit.bitwarden.Accessibility.AccessibilityService")]
|
|
||||||
public class AccessibilityService : Android.AccessibilityServices.AccessibilityService
|
|
||||||
{
|
|
||||||
private const string BitwardenPackage = "com.x8bit.bitwarden";
|
|
||||||
private const string BitwardenWebsite = "vault.bitwarden.com";
|
|
||||||
|
|
||||||
private IStorageService _storageService;
|
|
||||||
private IBroadcasterService _broadcasterService;
|
|
||||||
private DateTime? _lastSettingsReload = null;
|
|
||||||
private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1);
|
|
||||||
private HashSet<string> _blacklistedUris;
|
|
||||||
private AccessibilityNodeInfo _anchorNode = null;
|
|
||||||
private int _lastAnchorX = 0;
|
|
||||||
private int _lastAnchorY = 0;
|
|
||||||
private bool _isOverlayAboveAnchor = false;
|
|
||||||
private static bool _overlayAnchorObserverRunning = false;
|
|
||||||
private IWindowManager _windowManager = null;
|
|
||||||
private LinearLayout _overlayView = null;
|
|
||||||
private int _overlayViewHeight = 0;
|
|
||||||
private long _lastAutoFillTime = 0;
|
|
||||||
private Java.Lang.Runnable _overlayAnchorObserverRunnable = null;
|
|
||||||
private Handler _handler = new Handler(Looper.MainLooper);
|
|
||||||
|
|
||||||
private HashSet<string> _launcherPackageNames = null;
|
|
||||||
private DateTime? _lastLauncherSetBuilt = null;
|
|
||||||
private TimeSpan _rebuildLauncherSpan = TimeSpan.FromHours(1);
|
|
||||||
|
|
||||||
public override void OnCreate()
|
|
||||||
{
|
|
||||||
base.OnCreate();
|
|
||||||
LoadServices();
|
|
||||||
var settingsTask = LoadSettingsAsync();
|
|
||||||
_broadcasterService.Subscribe(nameof(AccessibilityService), (message) =>
|
|
||||||
{
|
|
||||||
if (message.Command == "OnAutofillTileClick")
|
|
||||||
{
|
|
||||||
var runnable = new Java.Lang.Runnable(OnAutofillTileClick);
|
|
||||||
_handler.PostDelayed(runnable, 250);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
AccessibilityHelpers.IsAccessibilityBroadcastReady = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnDestroy()
|
|
||||||
{
|
|
||||||
AccessibilityHelpers.IsAccessibilityBroadcastReady = false;
|
|
||||||
_broadcasterService.Unsubscribe(nameof(AccessibilityService));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var powerManager = GetSystemService(PowerService) as PowerManager;
|
|
||||||
if (Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch && !powerManager.IsInteractive)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop && !powerManager.IsScreenOn)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SkipPackage(e?.PackageName))
|
|
||||||
{
|
|
||||||
if (e?.PackageName != "com.android.systemui")
|
|
||||||
{
|
|
||||||
CancelOverlayPrompt();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccessibilityHelpers.PrintTestData(RootInActiveWindow, e);
|
|
||||||
|
|
||||||
LoadServices();
|
|
||||||
var settingsTask = LoadSettingsAsync();
|
|
||||||
AccessibilityNodeInfo root = null;
|
|
||||||
|
|
||||||
switch (e.EventType)
|
|
||||||
{
|
|
||||||
case EventTypes.ViewFocused:
|
|
||||||
case EventTypes.ViewClicked:
|
|
||||||
if (e.Source == null || e.PackageName == BitwardenPackage)
|
|
||||||
{
|
|
||||||
CancelOverlayPrompt();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
root = RootInActiveWindow;
|
|
||||||
if (root == null || root.PackageName != e.PackageName)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(e.Source?.Password ?? false) && !AccessibilityHelpers.IsUsernameEditText(root, e))
|
|
||||||
{
|
|
||||||
CancelOverlayPrompt();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ScanAndAutofill(root, e))
|
|
||||||
{
|
|
||||||
CancelOverlayPrompt();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
OverlayPromptToAutofill(root, e);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case EventTypes.WindowContentChanged:
|
|
||||||
case EventTypes.WindowStateChanged:
|
|
||||||
if (AccessibilityHelpers.LastCredentials == null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (e.PackageName == BitwardenPackage)
|
|
||||||
{
|
|
||||||
CancelOverlayPrompt();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
root = RootInActiveWindow;
|
|
||||||
if (root == null || root.PackageName != e.PackageName)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (ScanAndAutofill(root, e))
|
|
||||||
{
|
|
||||||
CancelOverlayPrompt();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Suppress exceptions so that service doesn't crash.
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnInterrupt()
|
|
||||||
{
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ScanAndAutofill(AccessibilityNodeInfo root, AccessibilityEvent e)
|
|
||||||
{
|
|
||||||
var filled = false;
|
|
||||||
var uri = AccessibilityHelpers.GetUri(root);
|
|
||||||
if (uri != null && !uri.Contains(BitwardenWebsite) &&
|
|
||||||
AccessibilityHelpers.NeedToAutofill(AccessibilityHelpers.LastCredentials, uri))
|
|
||||||
{
|
|
||||||
var allEditTexts = AccessibilityHelpers.GetWindowNodes(root, e, n => AccessibilityHelpers.EditText(n), false);
|
|
||||||
var usernameEditText = AccessibilityHelpers.GetUsernameEditText(uri, allEditTexts);
|
|
||||||
var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e, n => n.Password, false);
|
|
||||||
if (usernameEditText != null || passwordNodes.Count > 0)
|
|
||||||
{
|
|
||||||
AccessibilityHelpers.FillCredentials(usernameEditText, passwordNodes);
|
|
||||||
filled = true;
|
|
||||||
_lastAutoFillTime = Java.Lang.JavaSystem.CurrentTimeMillis();
|
|
||||||
AccessibilityHelpers.LastCredentials = null;
|
|
||||||
}
|
|
||||||
allEditTexts.Dispose();
|
|
||||||
passwordNodes.Dispose();
|
|
||||||
}
|
|
||||||
if (AccessibilityHelpers.LastCredentials != null)
|
|
||||||
{
|
|
||||||
Task.Run(async () =>
|
|
||||||
{
|
|
||||||
await Task.Delay(1000);
|
|
||||||
AccessibilityHelpers.LastCredentials = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return filled;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAutofillTileClick()
|
|
||||||
{
|
|
||||||
CancelOverlayPrompt();
|
|
||||||
|
|
||||||
var root = RootInActiveWindow;
|
|
||||||
if (root != null && root.PackageName != BitwardenPackage &&
|
|
||||||
root.PackageName != AccessibilityHelpers.SystemUiPackage &&
|
|
||||||
!SkipPackage(root.PackageName))
|
|
||||||
{
|
|
||||||
var uri = AccessibilityHelpers.GetUri(root);
|
|
||||||
if (!string.IsNullOrWhiteSpace(uri))
|
|
||||||
{
|
|
||||||
var intent = new Intent(this, typeof(AccessibilityActivity));
|
|
||||||
intent.PutExtra("uri", uri);
|
|
||||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
|
||||||
StartActivity(intent);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Toast.MakeText(this, AppResources.AutofillTileUriNotFound, ToastLength.Long).Show();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CancelOverlayPrompt()
|
|
||||||
{
|
|
||||||
_overlayAnchorObserverRunning = false;
|
|
||||||
|
|
||||||
if (_windowManager != null && _overlayView != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_windowManager.RemoveViewImmediate(_overlayView);
|
|
||||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Removed");
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
_overlayView = null;
|
|
||||||
_lastAnchorX = 0;
|
|
||||||
_lastAnchorY = 0;
|
|
||||||
_isOverlayAboveAnchor = false;
|
|
||||||
|
|
||||||
if (_anchorNode != null)
|
|
||||||
{
|
|
||||||
_anchorNode.Recycle();
|
|
||||||
_anchorNode = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OverlayPromptToAutofill(AccessibilityNodeInfo root, AccessibilityEvent e)
|
|
||||||
{
|
|
||||||
if (Java.Lang.JavaSystem.CurrentTimeMillis() - _lastAutoFillTime < 1000 ||
|
|
||||||
AccessibilityHelpers.IsAutofillServicePromptVisible(Windows))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!AccessibilityHelpers.OverlayPermitted())
|
|
||||||
{
|
|
||||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
|
|
||||||
{
|
|
||||||
// The user has the option of only using the autofill tile and leaving the overlay permission
|
|
||||||
// disabled, so only show this toast if they're using accessibility without overlay permission on
|
|
||||||
// a version of Android without quick-action tile support
|
|
||||||
System.Diagnostics.Debug.WriteLine(">>> Overlay Permission not granted");
|
|
||||||
Toast.MakeText(this, AppResources.AccessibilityDrawOverPermissionAlert, ToastLength.Long).Show();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_overlayView != null || _anchorNode != null || _overlayAnchorObserverRunning)
|
|
||||||
{
|
|
||||||
CancelOverlayPrompt();
|
|
||||||
}
|
|
||||||
|
|
||||||
var uri = AccessibilityHelpers.GetUri(root);
|
|
||||||
var fillable = !string.IsNullOrWhiteSpace(uri);
|
|
||||||
if (fillable)
|
|
||||||
{
|
|
||||||
if (_blacklistedUris != null && _blacklistedUris.Any())
|
|
||||||
{
|
|
||||||
if (Uri.TryCreate(uri, UriKind.Absolute, out var parsedUri) && parsedUri.Scheme.StartsWith("http"))
|
|
||||||
{
|
|
||||||
fillable = !_blacklistedUris.Contains(
|
|
||||||
string.Format("{0}://{1}", parsedUri.Scheme, parsedUri.Host));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
fillable = !_blacklistedUris.Contains(uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!fillable)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var intent = new Intent(this, typeof(AccessibilityActivity));
|
|
||||||
intent.PutExtra("uri", uri);
|
|
||||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
|
||||||
|
|
||||||
_overlayView = AccessibilityHelpers.GetOverlayView(this);
|
|
||||||
_overlayView.Measure(View.MeasureSpec.MakeMeasureSpec(0, 0),
|
|
||||||
View.MeasureSpec.MakeMeasureSpec(0, 0));
|
|
||||||
_overlayViewHeight = _overlayView.MeasuredHeight;
|
|
||||||
_overlayView.Click += (sender, eventArgs) =>
|
|
||||||
{
|
|
||||||
CancelOverlayPrompt();
|
|
||||||
StartActivity(intent);
|
|
||||||
};
|
|
||||||
|
|
||||||
var layoutParams = AccessibilityHelpers.GetOverlayLayoutParams();
|
|
||||||
var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(this, e.Source,
|
|
||||||
_overlayViewHeight, _isOverlayAboveAnchor);
|
|
||||||
layoutParams.X = anchorPosition.X;
|
|
||||||
layoutParams.Y = anchorPosition.Y;
|
|
||||||
|
|
||||||
if (_windowManager == null)
|
|
||||||
{
|
|
||||||
_windowManager = GetSystemService(WindowService).JavaCast<IWindowManager>();
|
|
||||||
}
|
|
||||||
|
|
||||||
_anchorNode = e.Source;
|
|
||||||
_lastAnchorX = anchorPosition.X;
|
|
||||||
_lastAnchorY = anchorPosition.Y;
|
|
||||||
|
|
||||||
_windowManager.AddView(_overlayView, layoutParams);
|
|
||||||
|
|
||||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Added at X:{0} Y:{1}",
|
|
||||||
layoutParams.X, layoutParams.Y);
|
|
||||||
|
|
||||||
StartOverlayAnchorObserver();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartOverlayAnchorObserver()
|
|
||||||
{
|
|
||||||
if (_overlayAnchorObserverRunning)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_overlayAnchorObserverRunning = true;
|
|
||||||
_overlayAnchorObserverRunnable = new Java.Lang.Runnable(() =>
|
|
||||||
{
|
|
||||||
if (_overlayAnchorObserverRunning)
|
|
||||||
{
|
|
||||||
AdjustOverlayForScroll();
|
|
||||||
_handler.PostDelayed(_overlayAnchorObserverRunnable, 250);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_handler.PostDelayed(_overlayAnchorObserverRunnable, 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AdjustOverlayForScroll()
|
|
||||||
{
|
|
||||||
if (_overlayView == null || _anchorNode == null ||
|
|
||||||
AccessibilityHelpers.IsAutofillServicePromptVisible(Windows))
|
|
||||||
{
|
|
||||||
CancelOverlayPrompt();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var root = RootInActiveWindow;
|
|
||||||
IEnumerable<AccessibilityWindowInfo> windows = null;
|
|
||||||
if (Build.VERSION.SdkInt > BuildVersionCodes.Kitkat)
|
|
||||||
{
|
|
||||||
windows = Windows;
|
|
||||||
}
|
|
||||||
|
|
||||||
var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(this, _anchorNode, root,
|
|
||||||
windows, _overlayViewHeight, _isOverlayAboveAnchor);
|
|
||||||
if (anchorPosition == null)
|
|
||||||
{
|
|
||||||
CancelOverlayPrompt();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (anchorPosition.X == -1 && anchorPosition.Y == -1)
|
|
||||||
{
|
|
||||||
if (_overlayView.Visibility != ViewStates.Gone)
|
|
||||||
{
|
|
||||||
_overlayView.Visibility = ViewStates.Gone;
|
|
||||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Hidden");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (anchorPosition.X == -1)
|
|
||||||
{
|
|
||||||
_isOverlayAboveAnchor = false;
|
|
||||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Below Anchor");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (anchorPosition.Y == -1)
|
|
||||||
{
|
|
||||||
_isOverlayAboveAnchor = true;
|
|
||||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Above Anchor");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (anchorPosition.X == _lastAnchorX && anchorPosition.Y == _lastAnchorY)
|
|
||||||
{
|
|
||||||
if (_overlayView.Visibility != ViewStates.Visible)
|
|
||||||
{
|
|
||||||
_overlayView.Visibility = ViewStates.Visible;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var layoutParams = AccessibilityHelpers.GetOverlayLayoutParams();
|
|
||||||
layoutParams.X = anchorPosition.X;
|
|
||||||
layoutParams.Y = anchorPosition.Y;
|
|
||||||
|
|
||||||
_lastAnchorX = anchorPosition.X;
|
|
||||||
_lastAnchorY = anchorPosition.Y;
|
|
||||||
|
|
||||||
_windowManager.UpdateViewLayout(_overlayView, layoutParams);
|
|
||||||
|
|
||||||
if (_overlayView.Visibility != ViewStates.Visible)
|
|
||||||
{
|
|
||||||
_overlayView.Visibility = ViewStates.Visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Updated to X:{0} Y:{1}",
|
|
||||||
layoutParams.X, layoutParams.Y);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool SkipPackage(string eventPackageName)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(eventPackageName) ||
|
|
||||||
AccessibilityHelpers.FilteredPackageNames.Contains(eventPackageName) ||
|
|
||||||
eventPackageName.Contains("launcher"))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (_launcherPackageNames == null || _lastLauncherSetBuilt == null ||
|
|
||||||
(DateTime.Now - _lastLauncherSetBuilt.Value) > _rebuildLauncherSpan)
|
|
||||||
{
|
|
||||||
// refresh launcher list every now and then
|
|
||||||
_lastLauncherSetBuilt = DateTime.Now;
|
|
||||||
var intent = new Intent(Intent.ActionMain);
|
|
||||||
intent.AddCategory(Intent.CategoryHome);
|
|
||||||
var resolveInfo = PackageManager.QueryIntentActivities(intent, 0);
|
|
||||||
_launcherPackageNames = resolveInfo.Select(ri => ri.ActivityInfo.PackageName).ToHashSet();
|
|
||||||
}
|
|
||||||
return _launcherPackageNames.Contains(eventPackageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadServices()
|
|
||||||
{
|
|
||||||
if (_storageService == null)
|
|
||||||
{
|
|
||||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
|
||||||
}
|
|
||||||
if (_broadcasterService == null)
|
|
||||||
{
|
|
||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadSettingsAsync()
|
|
||||||
{
|
|
||||||
var now = DateTime.UtcNow;
|
|
||||||
if (_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan)
|
|
||||||
{
|
|
||||||
_lastSettingsReload = now;
|
|
||||||
var uris = await _storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey);
|
|
||||||
if (uris != null)
|
|
||||||
{
|
|
||||||
_blacklistedUris = new HashSet<string>(uris);
|
|
||||||
}
|
|
||||||
var isAutoFillTileAdded = await _storageService.GetAsync<bool?>(Constants.AutofillTileAdded);
|
|
||||||
AccessibilityHelpers.IsAutofillTileAdded = isAutoFillTileAdded.GetValueOrDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Accessibility
|
|
||||||
{
|
|
||||||
public class Browser
|
|
||||||
{
|
|
||||||
public Browser(string packageName, string uriViewId)
|
|
||||||
{
|
|
||||||
PackageName = packageName;
|
|
||||||
UriViewId = uriViewId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Browser(string packageName, string uriViewId, Func<string, string> getUriFunction)
|
|
||||||
: this(packageName, uriViewId)
|
|
||||||
{
|
|
||||||
GetUriFunction = getUriFunction;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string PackageName { get; set; }
|
|
||||||
public string UriViewId { get; set; }
|
|
||||||
public Func<string, string> GetUriFunction { get; set; } = (s) => s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
namespace Bit.Droid.Accessibility
|
|
||||||
{
|
|
||||||
public class KnownUsernameField
|
|
||||||
{
|
|
||||||
public KnownUsernameField(string uriAuthority, (string UriPathWanted, string UsernameViewId)[] accessOptions)
|
|
||||||
{
|
|
||||||
UriAuthority = uriAuthority;
|
|
||||||
AccessOptions = accessOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string UriAuthority { get; set; }
|
|
||||||
public (string UriPathWanted, string UsernameViewId)[] AccessOptions { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Android.Views.Accessibility;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Accessibility
|
|
||||||
{
|
|
||||||
public class NodeList : List<AccessibilityNodeInfo>, IDisposable
|
|
||||||
{
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
foreach (var item in this)
|
|
||||||
{
|
|
||||||
item.Recycle();
|
|
||||||
item.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
19
src/Android/Assets/AboutAssets.txt
Normal file
19
src/Android/Assets/AboutAssets.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Any raw assets you want to be deployed with your application can be placed in
|
||||||
|
this directory (and child directories) and given a Build Action of "AndroidAsset".
|
||||||
|
|
||||||
|
These files will be deployed with you package and will be accessible using Android's
|
||||||
|
AssetManager, like this:
|
||||||
|
|
||||||
|
public class ReadAsset : Activity
|
||||||
|
{
|
||||||
|
protected override void OnCreate (Bundle bundle)
|
||||||
|
{
|
||||||
|
base.OnCreate (bundle);
|
||||||
|
|
||||||
|
InputStream input = Assets.Open ("my_asset.txt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Additionally, some Android functions will automatically load asset files:
|
||||||
|
|
||||||
|
Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,416 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Service.Autofill;
|
|
||||||
using Android.Widget;
|
|
||||||
using System.Linq;
|
|
||||||
using Android.App;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Android.App.Slices;
|
|
||||||
using Android.Graphics;
|
|
||||||
using Android.Graphics.Drawables;
|
|
||||||
using Android.OS;
|
|
||||||
using Android.Runtime;
|
|
||||||
using Android.Widget.Inline;
|
|
||||||
using Bit.App.Resources;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Android.Views.Autofill;
|
|
||||||
using AndroidX.AutoFill.Inline;
|
|
||||||
using AndroidX.AutoFill.Inline.V1;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using SaveFlags = Android.Service.Autofill.SaveFlags;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Autofill
|
|
||||||
{
|
|
||||||
public static class AutofillHelpers
|
|
||||||
{
|
|
||||||
private static int _pendingIntentId = 0;
|
|
||||||
|
|
||||||
// These browsers work natively with the Autofill Framework
|
|
||||||
//
|
|
||||||
// Be sure:
|
|
||||||
// - to keep these entries sorted alphabetically and
|
|
||||||
//
|
|
||||||
// - ... to keep this list in sync with values in AccessibilityHelpers.SupportedBrowsers [Section A], too.
|
|
||||||
public static HashSet<string> TrustedBrowsers = new HashSet<string>
|
|
||||||
{
|
|
||||||
"com.duckduckgo.mobile.android",
|
|
||||||
"com.google.android.googlequicksearchbox",
|
|
||||||
"org.mozilla.focus",
|
|
||||||
"org.mozilla.focus.beta",
|
|
||||||
"org.mozilla.focus.nightly",
|
|
||||||
"org.mozilla.klar",
|
|
||||||
};
|
|
||||||
|
|
||||||
// These browsers work using the compatibility shim for the Autofill Framework
|
|
||||||
//
|
|
||||||
// Be sure:
|
|
||||||
// - to keep these entries sorted alphabetically,
|
|
||||||
// - to keep this list in sync with values in Resources/xml/autofillservice.xml, and
|
|
||||||
//
|
|
||||||
// - ... to keep this list in sync with values in AccessibilityHelpers.SupportedBrowsers [Section A], too.
|
|
||||||
public static HashSet<string> CompatBrowsers = new HashSet<string>
|
|
||||||
{
|
|
||||||
"com.amazon.cloud9",
|
|
||||||
"com.android.browser",
|
|
||||||
"com.android.chrome",
|
|
||||||
"com.android.htmlviewer",
|
|
||||||
"com.avast.android.secure.browser",
|
|
||||||
"com.avg.android.secure.browser",
|
|
||||||
"com.brave.browser",
|
|
||||||
"com.brave.browser_beta",
|
|
||||||
"com.brave.browser_default",
|
|
||||||
"com.brave.browser_dev",
|
|
||||||
"com.brave.browser_nightly",
|
|
||||||
"com.chrome.beta",
|
|
||||||
"com.chrome.canary",
|
|
||||||
"com.chrome.dev",
|
|
||||||
"com.cookiegames.smartcookie",
|
|
||||||
"com.cookiejarapps.android.smartcookieweb",
|
|
||||||
"com.ecosia.android",
|
|
||||||
"com.google.android.apps.chrome",
|
|
||||||
"com.google.android.apps.chrome_dev",
|
|
||||||
"com.google.android.captiveportallogin",
|
|
||||||
"com.jamal2367.styx",
|
|
||||||
"com.kiwibrowser.browser",
|
|
||||||
"com.microsoft.emmx",
|
|
||||||
"com.microsoft.emmx.beta",
|
|
||||||
"com.microsoft.emmx.canary",
|
|
||||||
"com.microsoft.emmx.dev",
|
|
||||||
"com.mmbox.browser",
|
|
||||||
"com.mmbox.xbrowser",
|
|
||||||
"com.mycompany.app.soulbrowser",
|
|
||||||
"com.naver.whale",
|
|
||||||
"com.opera.browser",
|
|
||||||
"com.opera.browser.beta",
|
|
||||||
"com.opera.mini.native",
|
|
||||||
"com.opera.mini.native.beta",
|
|
||||||
"com.opera.touch",
|
|
||||||
"com.qflair.browserq",
|
|
||||||
"com.qwant.liberty",
|
|
||||||
"com.sec.android.app.sbrowser",
|
|
||||||
"com.sec.android.app.sbrowser.beta",
|
|
||||||
"com.stoutner.privacybrowser.free",
|
|
||||||
"com.stoutner.privacybrowser.standard",
|
|
||||||
"com.vivaldi.browser",
|
|
||||||
"com.vivaldi.browser.snapshot",
|
|
||||||
"com.vivaldi.browser.sopranos",
|
|
||||||
"com.yandex.browser",
|
|
||||||
"com.z28j.feel",
|
|
||||||
"idm.internet.download.manager",
|
|
||||||
"idm.internet.download.manager.adm.lite",
|
|
||||||
"idm.internet.download.manager.plus",
|
|
||||||
"io.github.forkmaintainers.iceraven",
|
|
||||||
"mark.via",
|
|
||||||
"mark.via.gp",
|
|
||||||
"net.slions.fulguris.full.download",
|
|
||||||
"net.slions.fulguris.full.download.debug",
|
|
||||||
"net.slions.fulguris.full.playstore",
|
|
||||||
"net.slions.fulguris.full.playstore.debug",
|
|
||||||
"org.adblockplus.browser",
|
|
||||||
"org.adblockplus.browser.beta",
|
|
||||||
"org.bromite.bromite",
|
|
||||||
"org.bromite.chromium",
|
|
||||||
"org.chromium.chrome",
|
|
||||||
"org.codeaurora.swe.browser",
|
|
||||||
"org.gnu.icecat",
|
|
||||||
"org.mozilla.fenix",
|
|
||||||
"org.mozilla.fenix.nightly",
|
|
||||||
"org.mozilla.fennec_aurora",
|
|
||||||
"org.mozilla.fennec_fdroid",
|
|
||||||
"org.mozilla.firefox",
|
|
||||||
"org.mozilla.firefox_beta",
|
|
||||||
"org.mozilla.reference.browser",
|
|
||||||
"org.mozilla.rocket",
|
|
||||||
"org.torproject.torbrowser",
|
|
||||||
"org.torproject.torbrowser_alpha",
|
|
||||||
"org.ungoogled.chromium.extensions.stable",
|
|
||||||
"org.ungoogled.chromium.stable",
|
|
||||||
"us.spotco.fennec_dos",
|
|
||||||
};
|
|
||||||
|
|
||||||
// The URLs are blacklisted from autofilling
|
|
||||||
public static HashSet<string> BlacklistedUris = new HashSet<string>
|
|
||||||
{
|
|
||||||
"androidapp://android",
|
|
||||||
"androidapp://com.android.settings",
|
|
||||||
"androidapp://com.x8bit.bitwarden",
|
|
||||||
"androidapp://com.oneplus.applocker",
|
|
||||||
};
|
|
||||||
|
|
||||||
public static async Task<List<FilledItem>> GetFillItemsAsync(Parser parser, ICipherService cipherService)
|
|
||||||
{
|
|
||||||
if (parser.FieldCollection.FillableForLogin)
|
|
||||||
{
|
|
||||||
var ciphers = await cipherService.GetAllDecryptedByUrlAsync(parser.Uri);
|
|
||||||
if (ciphers.Item1.Any() || ciphers.Item2.Any())
|
|
||||||
{
|
|
||||||
var allCiphers = ciphers.Item1.ToList();
|
|
||||||
allCiphers.AddRange(ciphers.Item2.ToList());
|
|
||||||
var nonPromptCiphers = allCiphers.Where(cipher => cipher.Reprompt == CipherRepromptType.None);
|
|
||||||
return nonPromptCiphers.Select(c => new FilledItem(c)).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (parser.FieldCollection.FillableForCard)
|
|
||||||
{
|
|
||||||
var ciphers = await cipherService.GetAllDecryptedAsync();
|
|
||||||
return ciphers.Where(c => c.Type == CipherType.Card && c.Reprompt == CipherRepromptType.None).Select(c => new FilledItem(c)).ToList();
|
|
||||||
}
|
|
||||||
return new List<FilledItem>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static FillResponse BuildFillResponse(Parser parser, List<FilledItem> items, bool locked,
|
|
||||||
bool inlineAutofillEnabled, FillRequest fillRequest = null)
|
|
||||||
{
|
|
||||||
// Acquire inline presentation specs on Android 11+
|
|
||||||
IList<InlinePresentationSpec> inlinePresentationSpecs = null;
|
|
||||||
var inlinePresentationSpecsCount = 0;
|
|
||||||
var inlineMaxSuggestedCount = 0;
|
|
||||||
if (inlineAutofillEnabled && fillRequest != null && (int)Build.VERSION.SdkInt >= 30)
|
|
||||||
{
|
|
||||||
var inlineSuggestionsRequest = fillRequest.InlineSuggestionsRequest;
|
|
||||||
inlineMaxSuggestedCount = inlineSuggestionsRequest?.MaxSuggestionCount ?? 0;
|
|
||||||
inlinePresentationSpecs = inlineSuggestionsRequest?.InlinePresentationSpecs;
|
|
||||||
inlinePresentationSpecsCount = inlinePresentationSpecs?.Count ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build response
|
|
||||||
var responseBuilder = new FillResponse.Builder();
|
|
||||||
if (items != null && items.Count > 0)
|
|
||||||
{
|
|
||||||
var maxItems = items.Count;
|
|
||||||
if (inlineMaxSuggestedCount > 0)
|
|
||||||
{
|
|
||||||
// -1 to adjust for 'open vault' option
|
|
||||||
maxItems = Math.Min(maxItems, inlineMaxSuggestedCount - 1);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < maxItems; i++)
|
|
||||||
{
|
|
||||||
InlinePresentationSpec inlinePresentationSpec = null;
|
|
||||||
if (inlinePresentationSpecs != null)
|
|
||||||
{
|
|
||||||
if (i < inlinePresentationSpecsCount)
|
|
||||||
{
|
|
||||||
inlinePresentationSpec = inlinePresentationSpecs[i];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If the max suggestion count is larger than the number of specs in the list, then
|
|
||||||
// the last spec is used for the remainder of the suggestions
|
|
||||||
inlinePresentationSpec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i],
|
|
||||||
inlinePresentationSpec);
|
|
||||||
if (dataset != null)
|
|
||||||
{
|
|
||||||
responseBuilder.AddDataset(dataset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
responseBuilder.AddDataset(BuildVaultDataset(parser.ApplicationContext, parser.FieldCollection,
|
|
||||||
parser.Uri, locked, inlinePresentationSpecs));
|
|
||||||
AddSaveInfo(parser, fillRequest, responseBuilder, parser.FieldCollection);
|
|
||||||
responseBuilder.SetIgnoredIds(parser.FieldCollection.IgnoreAutofillIds.ToArray());
|
|
||||||
return responseBuilder.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem,
|
|
||||||
InlinePresentationSpec inlinePresentationSpec = null)
|
|
||||||
{
|
|
||||||
var overlayPresentation = BuildOverlayPresentation(
|
|
||||||
filledItem.Name,
|
|
||||||
filledItem.Subtitle,
|
|
||||||
filledItem.Icon,
|
|
||||||
context);
|
|
||||||
|
|
||||||
var inlinePresentation = BuildInlinePresentation(
|
|
||||||
inlinePresentationSpec,
|
|
||||||
filledItem.Name,
|
|
||||||
filledItem.Subtitle,
|
|
||||||
filledItem.Icon,
|
|
||||||
null,
|
|
||||||
context);
|
|
||||||
|
|
||||||
var datasetBuilder = new Dataset.Builder(overlayPresentation);
|
|
||||||
if (inlinePresentation != null)
|
|
||||||
{
|
|
||||||
datasetBuilder.SetInlinePresentation(inlinePresentation);
|
|
||||||
}
|
|
||||||
if (filledItem.ApplyToFields(fields, datasetBuilder))
|
|
||||||
{
|
|
||||||
return datasetBuilder.Build();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Dataset BuildVaultDataset(Context context, FieldCollection fields, string uri, bool locked,
|
|
||||||
IList<InlinePresentationSpec> inlinePresentationSpecs = null)
|
|
||||||
{
|
|
||||||
var intent = new Intent(context, typeof(MainActivity));
|
|
||||||
intent.PutExtra("autofillFramework", true);
|
|
||||||
if (fields.FillableForLogin)
|
|
||||||
{
|
|
||||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Login);
|
|
||||||
}
|
|
||||||
else if (fields.FillableForCard)
|
|
||||||
{
|
|
||||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Card);
|
|
||||||
}
|
|
||||||
else if (fields.FillableForIdentity)
|
|
||||||
{
|
|
||||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Identity);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
intent.PutExtra("autofillFrameworkUri", uri);
|
|
||||||
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
|
|
||||||
PendingIntentFlags.CancelCurrent);
|
|
||||||
|
|
||||||
var overlayPresentation = BuildOverlayPresentation(
|
|
||||||
AppResources.AutofillWithBitwarden,
|
|
||||||
locked ? AppResources.VaultIsLocked : AppResources.GoToMyVault,
|
|
||||||
Resource.Drawable.icon,
|
|
||||||
context);
|
|
||||||
|
|
||||||
var inlinePresentation = BuildInlinePresentation(
|
|
||||||
inlinePresentationSpecs?.Last(),
|
|
||||||
AppResources.Bitwarden,
|
|
||||||
locked ? AppResources.VaultIsLocked : AppResources.MyVault,
|
|
||||||
Resource.Drawable.icon,
|
|
||||||
pendingIntent,
|
|
||||||
context);
|
|
||||||
|
|
||||||
var datasetBuilder = new Dataset.Builder(overlayPresentation);
|
|
||||||
if (inlinePresentation != null)
|
|
||||||
{
|
|
||||||
datasetBuilder.SetInlinePresentation(inlinePresentation);
|
|
||||||
}
|
|
||||||
datasetBuilder.SetAuthentication(pendingIntent?.IntentSender);
|
|
||||||
|
|
||||||
// Dataset must have a value set. We will reset this in the main activity when the real item is chosen.
|
|
||||||
foreach (var autofillId in fields.AutofillIds)
|
|
||||||
{
|
|
||||||
datasetBuilder.SetValue(autofillId, AutofillValue.ForText("PLACEHOLDER"));
|
|
||||||
}
|
|
||||||
return datasetBuilder.Build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RemoteViews BuildOverlayPresentation(string text, string subtext, int iconId, Context context)
|
|
||||||
{
|
|
||||||
var packageName = context.PackageName;
|
|
||||||
var view = new RemoteViews(packageName, Resource.Layout.autofill_listitem);
|
|
||||||
view.SetTextViewText(Resource.Id.text1, text);
|
|
||||||
view.SetTextViewText(Resource.Id.text2, subtext);
|
|
||||||
view.SetImageViewResource(Resource.Id.icon, iconId);
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InlinePresentation BuildInlinePresentation(InlinePresentationSpec inlinePresentationSpec,
|
|
||||||
string text, string subtext, int iconId, PendingIntent pendingIntent, Context context)
|
|
||||||
{
|
|
||||||
if ((int)Build.VERSION.SdkInt < 30 || inlinePresentationSpec == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (pendingIntent == null)
|
|
||||||
{
|
|
||||||
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the
|
|
||||||
// "my vault" presentation) so we're including an empty one here
|
|
||||||
pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
|
|
||||||
PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent);
|
|
||||||
}
|
|
||||||
var slice = CreateInlinePresentationSlice(
|
|
||||||
inlinePresentationSpec,
|
|
||||||
text,
|
|
||||||
subtext,
|
|
||||||
iconId,
|
|
||||||
"Autofill option",
|
|
||||||
pendingIntent,
|
|
||||||
context);
|
|
||||||
if (slice != null)
|
|
||||||
{
|
|
||||||
return new InlinePresentation(slice, inlinePresentationSpec, false);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Slice CreateInlinePresentationSlice(
|
|
||||||
InlinePresentationSpec inlinePresentationSpec,
|
|
||||||
string text,
|
|
||||||
string subtext,
|
|
||||||
int iconId,
|
|
||||||
string contentDescription,
|
|
||||||
PendingIntent pendingIntent,
|
|
||||||
Context context)
|
|
||||||
{
|
|
||||||
var imeStyle = inlinePresentationSpec.Style;
|
|
||||||
if (!UiVersions.GetVersions(imeStyle).Contains(UiVersions.InlineUiVersion1))
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var contentBuilder = InlineSuggestionUi.NewContentBuilder(pendingIntent)
|
|
||||||
.SetContentDescription(contentDescription);
|
|
||||||
if (!string.IsNullOrWhiteSpace(text))
|
|
||||||
{
|
|
||||||
contentBuilder.SetTitle(text);
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrWhiteSpace(subtext))
|
|
||||||
{
|
|
||||||
contentBuilder.SetSubtitle(subtext);
|
|
||||||
}
|
|
||||||
if (iconId > 0)
|
|
||||||
{
|
|
||||||
var icon = Icon.CreateWithResource(context, iconId);
|
|
||||||
if (icon != null)
|
|
||||||
{
|
|
||||||
if (iconId == Resource.Drawable.icon)
|
|
||||||
{
|
|
||||||
// Don't tint our logo
|
|
||||||
icon.SetTintBlendMode(BlendMode.Dst);
|
|
||||||
}
|
|
||||||
contentBuilder.SetStartIcon(icon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return contentBuilder.Build().JavaCast<InlineSuggestionUi.Content>()?.Slice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void AddSaveInfo(Parser parser, FillRequest fillRequest, FillResponse.Builder responseBuilder,
|
|
||||||
FieldCollection fields)
|
|
||||||
{
|
|
||||||
// Docs state that password fields cannot be reliably saved in Compat mode since they will show as
|
|
||||||
// masked values.
|
|
||||||
bool? compatRequest = null;
|
|
||||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.Q && fillRequest != null)
|
|
||||||
{
|
|
||||||
// Attempt to automatically establish compat request mode on Android 10+
|
|
||||||
compatRequest = (fillRequest.Flags | FillRequest.FlagCompatibilityModeRequest) == fillRequest.Flags;
|
|
||||||
}
|
|
||||||
var compatBrowser = compatRequest ?? CompatBrowsers.Contains(parser.PackageName);
|
|
||||||
if (compatBrowser && fields.SaveType == SaveDataType.Password)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var requiredIds = fields.GetRequiredSaveFields();
|
|
||||||
if (fields.SaveType == SaveDataType.Generic || requiredIds.Length == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var saveBuilder = new SaveInfo.Builder(fields.SaveType, requiredIds);
|
|
||||||
var optionalIds = fields.GetOptionalSaveIds();
|
|
||||||
if (optionalIds.Length > 0)
|
|
||||||
{
|
|
||||||
saveBuilder.SetOptionalIds(optionalIds);
|
|
||||||
}
|
|
||||||
if (compatBrowser)
|
|
||||||
{
|
|
||||||
saveBuilder.SetFlags(SaveFlags.SaveOnAllViewsInvisible);
|
|
||||||
}
|
|
||||||
responseBuilder.SetSaveInfo(saveBuilder.Build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
using Android;
|
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.OS;
|
|
||||||
using Android.Runtime;
|
|
||||||
using Android.Service.Autofill;
|
|
||||||
using Android.Widget;
|
|
||||||
using Bit.Core;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Autofill
|
|
||||||
{
|
|
||||||
[Service(Permission = Manifest.Permission.BindAutofillService, Label = "Bitwarden")]
|
|
||||||
[IntentFilter(new string[] { "android.service.autofill.AutofillService" })]
|
|
||||||
[MetaData("android.autofill", Resource = "@xml/autofillservice")]
|
|
||||||
[Register("com.x8bit.bitwarden.Autofill.AutofillService")]
|
|
||||||
public class AutofillService : Android.Service.Autofill.AutofillService
|
|
||||||
{
|
|
||||||
private ICipherService _cipherService;
|
|
||||||
private IVaultTimeoutService _vaultTimeoutService;
|
|
||||||
private IStorageService _storageService;
|
|
||||||
private IPolicyService _policyService;
|
|
||||||
private IUserService _userService;
|
|
||||||
|
|
||||||
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal,
|
|
||||||
FillCallback callback)
|
|
||||||
{
|
|
||||||
var structure = request.FillContexts?.LastOrDefault()?.Structure;
|
|
||||||
if (structure == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parser = new Parser(structure, ApplicationContext);
|
|
||||||
parser.Parse();
|
|
||||||
|
|
||||||
if (_storageService == null)
|
|
||||||
{
|
|
||||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
|
||||||
}
|
|
||||||
|
|
||||||
var shouldAutofill = await parser.ShouldAutofillAsync(_storageService);
|
|
||||||
if (!shouldAutofill)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var inlineAutofillEnabled = await _storageService.GetAsync<bool?>(Constants.InlineAutofillEnabledKey) ?? true;
|
|
||||||
|
|
||||||
if (_vaultTimeoutService == null)
|
|
||||||
{
|
|
||||||
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<FilledItem> items = null;
|
|
||||||
await _vaultTimeoutService.CheckVaultTimeoutAsync();
|
|
||||||
var locked = await _vaultTimeoutService.IsLockedAsync();
|
|
||||||
if (!locked)
|
|
||||||
{
|
|
||||||
if (_cipherService == null)
|
|
||||||
{
|
|
||||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
|
||||||
}
|
|
||||||
items = await AutofillHelpers.GetFillItemsAsync(parser, _cipherService);
|
|
||||||
}
|
|
||||||
|
|
||||||
// build response
|
|
||||||
var response = AutofillHelpers.BuildFillResponse(parser, items, locked, inlineAutofillEnabled, request);
|
|
||||||
callback.OnSuccess(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async override void OnSaveRequest(SaveRequest request, SaveCallback callback)
|
|
||||||
{
|
|
||||||
var structure = request.FillContexts?.LastOrDefault()?.Structure;
|
|
||||||
if (structure == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_storageService == null)
|
|
||||||
{
|
|
||||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
|
||||||
}
|
|
||||||
|
|
||||||
var disableSavePrompt = await _storageService.GetAsync<bool?>(Constants.AutofillDisableSavePromptKey);
|
|
||||||
if (disableSavePrompt.GetValueOrDefault())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_policyService ??= ServiceContainer.Resolve<IPolicyService>("policyService");
|
|
||||||
|
|
||||||
var personalOwnershipPolicyApplies = await _policyService.PolicyAppliesToUser(PolicyType.PersonalOwnership);
|
|
||||||
if (personalOwnershipPolicyApplies)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parser = new Parser(structure, ApplicationContext);
|
|
||||||
parser.Parse();
|
|
||||||
|
|
||||||
var savedItem = parser.FieldCollection.GetSavedItem();
|
|
||||||
if (savedItem == null)
|
|
||||||
{
|
|
||||||
Toast.MakeText(this, "Unable to save this form.", ToastLength.Short).Show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var intent = new Intent(this, typeof(MainActivity));
|
|
||||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTop);
|
|
||||||
intent.PutExtra("autofillFramework", true);
|
|
||||||
intent.PutExtra("autofillFrameworkSave", true);
|
|
||||||
intent.PutExtra("autofillFrameworkType", (int)savedItem.Type);
|
|
||||||
switch (savedItem.Type)
|
|
||||||
{
|
|
||||||
case CipherType.Login:
|
|
||||||
intent.PutExtra("autofillFrameworkName", parser.Uri
|
|
||||||
.Replace(Constants.AndroidAppProtocol, string.Empty)
|
|
||||||
.Replace("https://", string.Empty)
|
|
||||||
.Replace("http://", string.Empty));
|
|
||||||
intent.PutExtra("autofillFrameworkUri", parser.Uri);
|
|
||||||
intent.PutExtra("autofillFrameworkUsername", savedItem.Login.Username);
|
|
||||||
intent.PutExtra("autofillFrameworkPassword", savedItem.Login.Password);
|
|
||||||
break;
|
|
||||||
case CipherType.Card:
|
|
||||||
intent.PutExtra("autofillFrameworkCardName", savedItem.Card.Name);
|
|
||||||
intent.PutExtra("autofillFrameworkCardNumber", savedItem.Card.Number);
|
|
||||||
intent.PutExtra("autofillFrameworkCardExpMonth", savedItem.Card.ExpMonth);
|
|
||||||
intent.PutExtra("autofillFrameworkCardExpYear", savedItem.Card.ExpYear);
|
|
||||||
intent.PutExtra("autofillFrameworkCardCode", savedItem.Card.Code);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Toast.MakeText(this, "Unable to save this type of form.", ToastLength.Short).Show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
StartActivity(intent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Android.Service.Autofill;
|
|
||||||
using Android.Views;
|
|
||||||
using Android.Views.Autofill;
|
|
||||||
using static Android.App.Assist.AssistStructure;
|
|
||||||
using Android.Text;
|
|
||||||
using static Android.Views.ViewStructure;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Autofill
|
|
||||||
{
|
|
||||||
public class Field
|
|
||||||
{
|
|
||||||
private List<string> _hints;
|
|
||||||
|
|
||||||
public Field(ViewNode node)
|
|
||||||
{
|
|
||||||
Id = node.Id;
|
|
||||||
TrackingId = $"{node.Id}_{node.GetHashCode()}";
|
|
||||||
IdEntry = node.IdEntry;
|
|
||||||
AutofillId = node.AutofillId;
|
|
||||||
AutofillType = node.AutofillType;
|
|
||||||
InputType = node.InputType;
|
|
||||||
Focused = node.IsFocused;
|
|
||||||
Selected = node.IsSelected;
|
|
||||||
Clickable = node.IsClickable;
|
|
||||||
Visible = node.Visibility == ViewStates.Visible;
|
|
||||||
Hints = FilterForSupportedHints(node.GetAutofillHints());
|
|
||||||
Hint = node.Hint;
|
|
||||||
AutofillOptions = node.GetAutofillOptions()?.ToList();
|
|
||||||
HtmlInfo = node.HtmlInfo;
|
|
||||||
Node = node;
|
|
||||||
|
|
||||||
if (node.AutofillValue != null)
|
|
||||||
{
|
|
||||||
if (node.AutofillValue.IsList)
|
|
||||||
{
|
|
||||||
var autofillOptions = node.GetAutofillOptions();
|
|
||||||
if (autofillOptions != null && autofillOptions.Length > 0)
|
|
||||||
{
|
|
||||||
ListValue = node.AutofillValue.ListValue;
|
|
||||||
TextValue = autofillOptions[node.AutofillValue.ListValue];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (node.AutofillValue.IsDate)
|
|
||||||
{
|
|
||||||
DateValue = node.AutofillValue.DateValue;
|
|
||||||
}
|
|
||||||
else if (node.AutofillValue.IsText)
|
|
||||||
{
|
|
||||||
TextValue = node.AutofillValue.TextValue;
|
|
||||||
}
|
|
||||||
else if (node.AutofillValue.IsToggle)
|
|
||||||
{
|
|
||||||
ToggleValue = node.AutofillValue.ToggleValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SaveDataType SaveType { get; set; } = SaveDataType.Generic;
|
|
||||||
public List<string> Hints
|
|
||||||
{
|
|
||||||
get => _hints;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_hints = value;
|
|
||||||
UpdateSaveTypeFromHints();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public string Hint { get; set; }
|
|
||||||
public int Id { get; private set; }
|
|
||||||
public string TrackingId { get; private set; }
|
|
||||||
public string IdEntry { get; set; }
|
|
||||||
public AutofillId AutofillId { get; private set; }
|
|
||||||
public AutofillType AutofillType { get; private set; }
|
|
||||||
public InputTypes InputType { get; private set; }
|
|
||||||
public bool Focused { get; private set; }
|
|
||||||
public bool Selected { get; private set; }
|
|
||||||
public bool Clickable { get; private set; }
|
|
||||||
public bool Visible { get; private set; }
|
|
||||||
public List<string> AutofillOptions { get; set; }
|
|
||||||
public string TextValue { get; set; }
|
|
||||||
public long? DateValue { get; set; }
|
|
||||||
public int? ListValue { get; set; }
|
|
||||||
public bool? ToggleValue { get; set; }
|
|
||||||
public HtmlInfo HtmlInfo { get; private set; }
|
|
||||||
public ViewNode Node { get; private set; }
|
|
||||||
|
|
||||||
public bool ValueIsNull()
|
|
||||||
{
|
|
||||||
return TextValue == null && DateValue == null && ToggleValue == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
if (this == obj)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null || GetType() != obj.GetType())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var field = obj as Field;
|
|
||||||
if (TextValue != null ? !TextValue.Equals(field.TextValue) : field.TextValue != null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (DateValue != null ? !DateValue.Equals(field.DateValue) : field.DateValue != null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return ToggleValue != null ? ToggleValue.Equals(field.ToggleValue) : field.ToggleValue == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
var result = TextValue != null ? TextValue.GetHashCode() : 0;
|
|
||||||
result = 31 * result + (DateValue != null ? DateValue.GetHashCode() : 0);
|
|
||||||
result = 31 * result + (ToggleValue != null ? ToggleValue.GetHashCode() : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<string> FilterForSupportedHints(string[] hints)
|
|
||||||
{
|
|
||||||
return hints?.Where(h => IsValidHint(h)).ToList() ?? new List<string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsValidHint(string hint)
|
|
||||||
{
|
|
||||||
switch (hint)
|
|
||||||
{
|
|
||||||
case View.AutofillHintCreditCardExpirationDate:
|
|
||||||
case View.AutofillHintCreditCardExpirationDay:
|
|
||||||
case View.AutofillHintCreditCardExpirationMonth:
|
|
||||||
case View.AutofillHintCreditCardExpirationYear:
|
|
||||||
case View.AutofillHintCreditCardNumber:
|
|
||||||
case View.AutofillHintCreditCardSecurityCode:
|
|
||||||
case View.AutofillHintEmailAddress:
|
|
||||||
case View.AutofillHintPhone:
|
|
||||||
case View.AutofillHintName:
|
|
||||||
case View.AutofillHintPassword:
|
|
||||||
case View.AutofillHintPostalAddress:
|
|
||||||
case View.AutofillHintPostalCode:
|
|
||||||
case View.AutofillHintUsername:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateSaveTypeFromHints()
|
|
||||||
{
|
|
||||||
SaveType = SaveDataType.Generic;
|
|
||||||
if (_hints == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var hint in _hints)
|
|
||||||
{
|
|
||||||
switch (hint)
|
|
||||||
{
|
|
||||||
case View.AutofillHintCreditCardExpirationDate:
|
|
||||||
case View.AutofillHintCreditCardExpirationDay:
|
|
||||||
case View.AutofillHintCreditCardExpirationMonth:
|
|
||||||
case View.AutofillHintCreditCardExpirationYear:
|
|
||||||
case View.AutofillHintCreditCardNumber:
|
|
||||||
case View.AutofillHintCreditCardSecurityCode:
|
|
||||||
SaveType |= SaveDataType.CreditCard;
|
|
||||||
break;
|
|
||||||
case View.AutofillHintEmailAddress:
|
|
||||||
SaveType |= SaveDataType.EmailAddress;
|
|
||||||
break;
|
|
||||||
case View.AutofillHintPhone:
|
|
||||||
case View.AutofillHintName:
|
|
||||||
SaveType |= SaveDataType.Generic;
|
|
||||||
break;
|
|
||||||
case View.AutofillHintPassword:
|
|
||||||
SaveType |= SaveDataType.Password;
|
|
||||||
SaveType &= ~SaveDataType.EmailAddress;
|
|
||||||
SaveType &= ~SaveDataType.Username;
|
|
||||||
break;
|
|
||||||
case View.AutofillHintPostalAddress:
|
|
||||||
case View.AutofillHintPostalCode:
|
|
||||||
SaveType |= SaveDataType.Address;
|
|
||||||
break;
|
|
||||||
case View.AutofillHintUsername:
|
|
||||||
SaveType |= SaveDataType.Username;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,342 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Android.Service.Autofill;
|
|
||||||
using Android.Views.Autofill;
|
|
||||||
using System.Linq;
|
|
||||||
using Android.Text;
|
|
||||||
using Android.Views;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Autofill
|
|
||||||
{
|
|
||||||
public class FieldCollection
|
|
||||||
{
|
|
||||||
private List<Field> _passwordFields = null;
|
|
||||||
private List<Field> _usernameFields = null;
|
|
||||||
private HashSet<string> _ignoreSearchTerms = new HashSet<string> { "search", "find", "recipient", "edit" };
|
|
||||||
private HashSet<string> _passwordTerms = new HashSet<string> { "password", "pswd" };
|
|
||||||
|
|
||||||
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
|
|
||||||
public SaveDataType SaveType
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (FillableForLogin)
|
|
||||||
{
|
|
||||||
return SaveDataType.Password;
|
|
||||||
}
|
|
||||||
else if (FillableForCard)
|
|
||||||
{
|
|
||||||
return SaveDataType.CreditCard;
|
|
||||||
}
|
|
||||||
|
|
||||||
return SaveDataType.Generic;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public HashSet<string> Hints { get; private set; } = new HashSet<string>();
|
|
||||||
public HashSet<string> FocusedHints { get; private set; } = new HashSet<string>();
|
|
||||||
public HashSet<string> FieldTrackingIds { get; private set; } = new HashSet<string>();
|
|
||||||
public List<Field> Fields { get; private set; } = new List<Field>();
|
|
||||||
public IDictionary<string, List<Field>> HintToFieldsMap { get; private set; } =
|
|
||||||
new Dictionary<string, List<Field>>();
|
|
||||||
public List<AutofillId> IgnoreAutofillIds { get; private set; } = new List<AutofillId>();
|
|
||||||
|
|
||||||
public List<Field> PasswordFields
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_passwordFields != null)
|
|
||||||
{
|
|
||||||
return _passwordFields;
|
|
||||||
}
|
|
||||||
if (Hints.Any())
|
|
||||||
{
|
|
||||||
_passwordFields = new List<Field>();
|
|
||||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
|
|
||||||
{
|
|
||||||
_passwordFields.AddRange(HintToFieldsMap[View.AutofillHintPassword]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
|
|
||||||
if (!_passwordFields.Any())
|
|
||||||
{
|
|
||||||
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _passwordFields;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Field> UsernameFields
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_usernameFields != null)
|
|
||||||
{
|
|
||||||
return _usernameFields;
|
|
||||||
}
|
|
||||||
_usernameFields = new List<Field>();
|
|
||||||
if (Hints.Any())
|
|
||||||
{
|
|
||||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintEmailAddress))
|
|
||||||
{
|
|
||||||
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintEmailAddress]);
|
|
||||||
}
|
|
||||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintUsername))
|
|
||||||
{
|
|
||||||
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintUsername]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreach (var passwordField in PasswordFields)
|
|
||||||
{
|
|
||||||
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
|
|
||||||
.LastOrDefault();
|
|
||||||
if (usernameField != null)
|
|
||||||
{
|
|
||||||
_usernameFields.Add(usernameField);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _usernameFields;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool FillableForLogin => FocusedHintsContain(new string[] {
|
|
||||||
View.AutofillHintUsername,
|
|
||||||
View.AutofillHintEmailAddress,
|
|
||||||
View.AutofillHintPassword
|
|
||||||
}) || UsernameFields.Any(f => f.Focused) || PasswordFields.Any(f => f.Focused);
|
|
||||||
|
|
||||||
public bool FillableForCard => FocusedHintsContain(new string[] {
|
|
||||||
View.AutofillHintCreditCardNumber,
|
|
||||||
View.AutofillHintCreditCardExpirationMonth,
|
|
||||||
View.AutofillHintCreditCardExpirationYear,
|
|
||||||
View.AutofillHintCreditCardSecurityCode
|
|
||||||
});
|
|
||||||
|
|
||||||
public bool FillableForIdentity => FocusedHintsContain(new string[] {
|
|
||||||
View.AutofillHintName,
|
|
||||||
View.AutofillHintPhone,
|
|
||||||
View.AutofillHintPostalAddress,
|
|
||||||
View.AutofillHintPostalCode
|
|
||||||
});
|
|
||||||
|
|
||||||
public bool Fillable => FillableForLogin || FillableForCard || FillableForIdentity;
|
|
||||||
|
|
||||||
public void Add(Field field)
|
|
||||||
{
|
|
||||||
if (field == null || FieldTrackingIds.Contains(field.TrackingId))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_passwordFields = _usernameFields = null;
|
|
||||||
FieldTrackingIds.Add(field.TrackingId);
|
|
||||||
Fields.Add(field);
|
|
||||||
AutofillIds.Add(field.AutofillId);
|
|
||||||
|
|
||||||
if (field.Hints != null)
|
|
||||||
{
|
|
||||||
foreach (var hint in field.Hints)
|
|
||||||
{
|
|
||||||
Hints.Add(hint);
|
|
||||||
if (field.Focused)
|
|
||||||
{
|
|
||||||
FocusedHints.Add(hint);
|
|
||||||
}
|
|
||||||
if (!HintToFieldsMap.ContainsKey(hint))
|
|
||||||
{
|
|
||||||
HintToFieldsMap.Add(hint, new List<Field>());
|
|
||||||
}
|
|
||||||
HintToFieldsMap[hint].Add(field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SavedItem GetSavedItem()
|
|
||||||
{
|
|
||||||
if (SaveType == SaveDataType.Password)
|
|
||||||
{
|
|
||||||
var passwordField = PasswordFields.FirstOrDefault(f => !string.IsNullOrWhiteSpace(f.TextValue));
|
|
||||||
if (passwordField == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var savedItem = new SavedItem
|
|
||||||
{
|
|
||||||
Type = Core.Enums.CipherType.Login,
|
|
||||||
Login = new SavedItem.LoginItem
|
|
||||||
{
|
|
||||||
Password = GetFieldValue(passwordField)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId).LastOrDefault();
|
|
||||||
savedItem.Login.Username = GetFieldValue(usernameField);
|
|
||||||
return savedItem;
|
|
||||||
}
|
|
||||||
else if (SaveType == SaveDataType.CreditCard)
|
|
||||||
{
|
|
||||||
var savedItem = new SavedItem
|
|
||||||
{
|
|
||||||
Type = Core.Enums.CipherType.Card,
|
|
||||||
Card = new SavedItem.CardItem
|
|
||||||
{
|
|
||||||
Number = GetFieldValue(View.AutofillHintCreditCardNumber),
|
|
||||||
Name = GetFieldValue(View.AutofillHintName),
|
|
||||||
ExpMonth = GetFieldValue(View.AutofillHintCreditCardExpirationMonth, true),
|
|
||||||
ExpYear = GetFieldValue(View.AutofillHintCreditCardExpirationYear),
|
|
||||||
Code = GetFieldValue(View.AutofillHintCreditCardSecurityCode)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return savedItem;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AutofillId[] GetOptionalSaveIds()
|
|
||||||
{
|
|
||||||
if (SaveType == SaveDataType.Password)
|
|
||||||
{
|
|
||||||
return UsernameFields.Select(f => f.AutofillId).ToArray();
|
|
||||||
}
|
|
||||||
else if (SaveType == SaveDataType.CreditCard)
|
|
||||||
{
|
|
||||||
var fieldList = new List<Field>();
|
|
||||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardSecurityCode))
|
|
||||||
{
|
|
||||||
fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardSecurityCode]);
|
|
||||||
}
|
|
||||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationYear))
|
|
||||||
{
|
|
||||||
fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardExpirationYear]);
|
|
||||||
}
|
|
||||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationMonth))
|
|
||||||
{
|
|
||||||
fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardExpirationMonth]);
|
|
||||||
}
|
|
||||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintName))
|
|
||||||
{
|
|
||||||
fieldList.AddRange(HintToFieldsMap[View.AutofillHintName]);
|
|
||||||
}
|
|
||||||
return fieldList.Select(f => f.AutofillId).ToArray();
|
|
||||||
}
|
|
||||||
return new AutofillId[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public AutofillId[] GetRequiredSaveFields()
|
|
||||||
{
|
|
||||||
if (SaveType == SaveDataType.Password)
|
|
||||||
{
|
|
||||||
return PasswordFields.Select(f => f.AutofillId).ToArray();
|
|
||||||
}
|
|
||||||
else if (SaveType == SaveDataType.CreditCard && HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardNumber))
|
|
||||||
{
|
|
||||||
return HintToFieldsMap[View.AutofillHintCreditCardNumber].Select(f => f.AutofillId).ToArray();
|
|
||||||
}
|
|
||||||
return new AutofillId[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool FocusedHintsContain(IEnumerable<string> hints)
|
|
||||||
{
|
|
||||||
return hints.Any(h => FocusedHints.Contains(h));
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetFieldValue(string hint, bool monthValue = false)
|
|
||||||
{
|
|
||||||
if (HintToFieldsMap.ContainsKey(hint))
|
|
||||||
{
|
|
||||||
foreach (var field in HintToFieldsMap[hint])
|
|
||||||
{
|
|
||||||
var val = GetFieldValue(field, monthValue);
|
|
||||||
if (!string.IsNullOrWhiteSpace(val))
|
|
||||||
{
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetFieldValue(Field field, bool monthValue = false)
|
|
||||||
{
|
|
||||||
if (field == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrWhiteSpace(field.TextValue))
|
|
||||||
{
|
|
||||||
if (field.AutofillType == AutofillType.List && field.ListValue.HasValue && monthValue)
|
|
||||||
{
|
|
||||||
if (field.AutofillOptions.Count == 13)
|
|
||||||
{
|
|
||||||
return field.ListValue.ToString();
|
|
||||||
}
|
|
||||||
else if (field.AutofillOptions.Count == 12)
|
|
||||||
{
|
|
||||||
return (field.ListValue + 1).ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return field.TextValue;
|
|
||||||
}
|
|
||||||
else if (field.DateValue.HasValue)
|
|
||||||
{
|
|
||||||
return field.DateValue.Value.ToString();
|
|
||||||
}
|
|
||||||
else if (field.ToggleValue.HasValue)
|
|
||||||
{
|
|
||||||
return field.ToggleValue.Value.ToString();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool FieldIsPassword(Field f)
|
|
||||||
{
|
|
||||||
var inputTypePassword = f.InputType.HasFlag(InputTypes.TextVariationPassword) ||
|
|
||||||
f.InputType.HasFlag(InputTypes.TextVariationVisiblePassword) ||
|
|
||||||
f.InputType.HasFlag(InputTypes.TextVariationWebPassword);
|
|
||||||
|
|
||||||
// For whatever reason, multi-line input types are coming through with TextVariationPassword flags
|
|
||||||
if (inputTypePassword && f.InputType.HasFlag(InputTypes.TextVariationPassword) &&
|
|
||||||
f.InputType.HasFlag(InputTypes.TextFlagMultiLine))
|
|
||||||
{
|
|
||||||
inputTypePassword = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!inputTypePassword && f.HtmlInfo != null && f.HtmlInfo.Tag == "input" &&
|
|
||||||
(f.HtmlInfo.Attributes?.Any() ?? false))
|
|
||||||
{
|
|
||||||
foreach (var a in f.HtmlInfo.Attributes)
|
|
||||||
{
|
|
||||||
var key = a.First as Java.Lang.String;
|
|
||||||
var val = a.Second as Java.Lang.String;
|
|
||||||
if (key != null && val != null && key.ToString() == "type" && val.ToString() == "password")
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return inputTypePassword && !ValueContainsAnyTerms(f.IdEntry, _ignoreSearchTerms) &&
|
|
||||||
!ValueContainsAnyTerms(f.Hint, _ignoreSearchTerms);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool FieldHasPasswordTerms(Field f)
|
|
||||||
{
|
|
||||||
return ValueContainsAnyTerms(f.IdEntry, _passwordTerms) || ValueContainsAnyTerms(f.Hint, _passwordTerms);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ValueContainsAnyTerms(string value, HashSet<string> terms)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var lowerValue = value.ToLowerInvariant();
|
|
||||||
return terms.Any(t => lowerValue.Contains(t));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,224 +0,0 @@
|
|||||||
using Android.Service.Autofill;
|
|
||||||
using Android.Views.Autofill;
|
|
||||||
using System.Linq;
|
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Android.Views;
|
|
||||||
using Bit.Core.Models.View;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Autofill
|
|
||||||
{
|
|
||||||
public class FilledItem
|
|
||||||
{
|
|
||||||
private string _password;
|
|
||||||
private string _cardName;
|
|
||||||
private string _cardNumber;
|
|
||||||
private string _cardExpMonth;
|
|
||||||
private string _cardExpYear;
|
|
||||||
private string _cardCode;
|
|
||||||
private string _idPhone;
|
|
||||||
private string _idEmail;
|
|
||||||
private string _idUsername;
|
|
||||||
private string _idAddress;
|
|
||||||
private string _idPostalCode;
|
|
||||||
|
|
||||||
public FilledItem(CipherView cipher)
|
|
||||||
{
|
|
||||||
Name = cipher.Name;
|
|
||||||
Type = cipher.Type;
|
|
||||||
Subtitle = cipher.SubTitle;
|
|
||||||
|
|
||||||
switch (Type)
|
|
||||||
{
|
|
||||||
case CipherType.Login:
|
|
||||||
Icon = Resource.Drawable.login;
|
|
||||||
_password = cipher.Login.Password;
|
|
||||||
break;
|
|
||||||
case CipherType.Card:
|
|
||||||
_cardNumber = cipher.Card.Number;
|
|
||||||
Icon = Resource.Drawable.card;
|
|
||||||
_cardName = cipher.Card.CardholderName;
|
|
||||||
_cardCode = cipher.Card.Code;
|
|
||||||
_cardExpMonth = cipher.Card.ExpMonth;
|
|
||||||
_cardExpYear = cipher.Card.ExpYear;
|
|
||||||
break;
|
|
||||||
case CipherType.Identity:
|
|
||||||
Icon = Resource.Drawable.id;
|
|
||||||
_idPhone = cipher.Identity.Phone;
|
|
||||||
_idEmail = cipher.Identity.Email;
|
|
||||||
_idUsername = cipher.Identity.Username;
|
|
||||||
_idAddress = cipher.Identity.FullAddress;
|
|
||||||
_idPostalCode = cipher.Identity.PostalCode;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Icon = Resource.Drawable.login;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Subtitle { get; set; } = string.Empty;
|
|
||||||
public int Icon { get; set; } = Resource.Drawable.login;
|
|
||||||
public CipherType Type { get; set; }
|
|
||||||
|
|
||||||
public bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder)
|
|
||||||
{
|
|
||||||
if (!fieldCollection?.Fields.Any() ?? true)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var setValues = false;
|
|
||||||
if (Type == CipherType.Login)
|
|
||||||
{
|
|
||||||
if (fieldCollection.PasswordFields.Any() && !string.IsNullOrWhiteSpace(_password))
|
|
||||||
{
|
|
||||||
foreach (var f in fieldCollection.PasswordFields)
|
|
||||||
{
|
|
||||||
var val = ApplyValue(f, _password);
|
|
||||||
if (val != null)
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
datasetBuilder.SetValue(f.AutofillId, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fieldCollection.UsernameFields.Any() && !string.IsNullOrWhiteSpace(Subtitle))
|
|
||||||
{
|
|
||||||
foreach (var f in fieldCollection.UsernameFields)
|
|
||||||
{
|
|
||||||
var val = ApplyValue(f, Subtitle);
|
|
||||||
if (val != null)
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
datasetBuilder.SetValue(f.AutofillId, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Type == CipherType.Card)
|
|
||||||
{
|
|
||||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardNumber,
|
|
||||||
_cardNumber))
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
}
|
|
||||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardSecurityCode,
|
|
||||||
_cardCode))
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
}
|
|
||||||
if (ApplyValue(datasetBuilder, fieldCollection,
|
|
||||||
Android.Views.View.AutofillHintCreditCardExpirationMonth, _cardExpMonth, true))
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
}
|
|
||||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardExpirationYear,
|
|
||||||
_cardExpYear))
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
}
|
|
||||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, _cardName))
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Type == CipherType.Identity)
|
|
||||||
{
|
|
||||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPhone, _idPhone))
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
}
|
|
||||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintEmailAddress, _idEmail))
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
}
|
|
||||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintUsername,
|
|
||||||
_idUsername))
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
}
|
|
||||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalAddress,
|
|
||||||
_idAddress))
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
}
|
|
||||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalCode,
|
|
||||||
_idPostalCode))
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
}
|
|
||||||
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, Subtitle))
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return setValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool ApplyValue(Dataset.Builder builder, FieldCollection fieldCollection,
|
|
||||||
string hint, string value, bool monthValue = false)
|
|
||||||
{
|
|
||||||
bool setValues = false;
|
|
||||||
if (fieldCollection.HintToFieldsMap.ContainsKey(hint) && !string.IsNullOrWhiteSpace(value))
|
|
||||||
{
|
|
||||||
foreach (var f in fieldCollection.HintToFieldsMap[hint])
|
|
||||||
{
|
|
||||||
var val = ApplyValue(f, value, monthValue);
|
|
||||||
if (val != null)
|
|
||||||
{
|
|
||||||
setValues = true;
|
|
||||||
builder.SetValue(f.AutofillId, val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return setValues;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AutofillValue ApplyValue(Field field, string value, bool monthValue = false)
|
|
||||||
{
|
|
||||||
switch (field.AutofillType)
|
|
||||||
{
|
|
||||||
case AutofillType.Date:
|
|
||||||
if (long.TryParse(value, out long dateValue))
|
|
||||||
{
|
|
||||||
return AutofillValue.ForDate(dateValue);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AutofillType.List:
|
|
||||||
if (field.AutofillOptions != null)
|
|
||||||
{
|
|
||||||
if (monthValue && int.TryParse(value, out int monthIndex))
|
|
||||||
{
|
|
||||||
if (field.AutofillOptions.Count == 13)
|
|
||||||
{
|
|
||||||
return AutofillValue.ForList(monthIndex);
|
|
||||||
}
|
|
||||||
else if (field.AutofillOptions.Count >= monthIndex)
|
|
||||||
{
|
|
||||||
return AutofillValue.ForList(monthIndex - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var i = 0; i < field.AutofillOptions.Count; i++)
|
|
||||||
{
|
|
||||||
if (field.AutofillOptions[i].Equals(value))
|
|
||||||
{
|
|
||||||
return AutofillValue.ForList(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case AutofillType.Text:
|
|
||||||
return AutofillValue.ForText(value);
|
|
||||||
case AutofillType.Toggle:
|
|
||||||
if (bool.TryParse(value, out bool toggleValue))
|
|
||||||
{
|
|
||||||
return AutofillValue.ForToggle(toggleValue);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
using static Android.App.Assist.AssistStructure;
|
|
||||||
using Android.App.Assist;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Bit.Core;
|
|
||||||
using Android.Content;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Android.OS;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Autofill
|
|
||||||
{
|
|
||||||
public class Parser
|
|
||||||
{
|
|
||||||
public static HashSet<string> _excludedPackageIds = new HashSet<string>
|
|
||||||
{
|
|
||||||
"android"
|
|
||||||
};
|
|
||||||
private readonly AssistStructure _structure;
|
|
||||||
private string _uri;
|
|
||||||
private string _packageName;
|
|
||||||
private string _website;
|
|
||||||
|
|
||||||
public Parser(AssistStructure structure, Context applicationContext)
|
|
||||||
{
|
|
||||||
_structure = structure;
|
|
||||||
ApplicationContext = applicationContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Context ApplicationContext { get; set; }
|
|
||||||
public FieldCollection FieldCollection { get; private set; } = new FieldCollection();
|
|
||||||
|
|
||||||
public string Uri
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(_uri))
|
|
||||||
{
|
|
||||||
return _uri;
|
|
||||||
}
|
|
||||||
var websiteNull = string.IsNullOrWhiteSpace(Website);
|
|
||||||
if (websiteNull && string.IsNullOrWhiteSpace(PackageName))
|
|
||||||
{
|
|
||||||
_uri = null;
|
|
||||||
}
|
|
||||||
else if (!websiteNull)
|
|
||||||
{
|
|
||||||
_uri = Website;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_uri = string.Concat(Constants.AndroidAppProtocol, PackageName);
|
|
||||||
}
|
|
||||||
return _uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string PackageName
|
|
||||||
{
|
|
||||||
get => _packageName;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
|
||||||
{
|
|
||||||
_packageName = _uri = null;
|
|
||||||
}
|
|
||||||
_packageName = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Website
|
|
||||||
{
|
|
||||||
get => _website;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
|
||||||
{
|
|
||||||
_website = _uri = null;
|
|
||||||
}
|
|
||||||
_website = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> ShouldAutofillAsync(IStorageService storageService)
|
|
||||||
{
|
|
||||||
var fillable = !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) &&
|
|
||||||
FieldCollection != null && FieldCollection.Fillable;
|
|
||||||
if (fillable)
|
|
||||||
{
|
|
||||||
var blacklistedUris = await storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey);
|
|
||||||
if (blacklistedUris != null && blacklistedUris.Count > 0)
|
|
||||||
{
|
|
||||||
fillable = !new HashSet<string>(blacklistedUris).Contains(Uri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fillable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Parse()
|
|
||||||
{
|
|
||||||
string titlePackageId = null;
|
|
||||||
for (var i = 0; i < _structure.WindowNodeCount; i++)
|
|
||||||
{
|
|
||||||
var node = _structure.GetWindowNodeAt(i);
|
|
||||||
if (i == 0)
|
|
||||||
{
|
|
||||||
titlePackageId = GetTitlePackageId(node);
|
|
||||||
}
|
|
||||||
ParseNode(node.RootViewNode);
|
|
||||||
}
|
|
||||||
if (string.IsNullOrWhiteSpace(PackageName) && string.IsNullOrWhiteSpace(Website))
|
|
||||||
{
|
|
||||||
PackageName = titlePackageId;
|
|
||||||
}
|
|
||||||
if (!AutofillHelpers.TrustedBrowsers.Contains(PackageName) &&
|
|
||||||
!AutofillHelpers.CompatBrowsers.Contains(PackageName))
|
|
||||||
{
|
|
||||||
Website = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseNode(ViewNode node)
|
|
||||||
{
|
|
||||||
SetPackageAndDomain(node);
|
|
||||||
var hints = node.GetAutofillHints();
|
|
||||||
var isEditText = node.ClassName == "android.widget.EditText" || node?.HtmlInfo?.Tag == "input";
|
|
||||||
if (isEditText || (hints?.Length ?? 0) > 0)
|
|
||||||
{
|
|
||||||
FieldCollection.Add(new Field(node));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
FieldCollection.IgnoreAutofillIds.Add(node.AutofillId);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < node.ChildCount; i++)
|
|
||||||
{
|
|
||||||
ParseNode(node.GetChildAt(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetPackageAndDomain(ViewNode node)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(PackageName) && !string.IsNullOrWhiteSpace(node.IdPackage) &&
|
|
||||||
!_excludedPackageIds.Contains(node.IdPackage))
|
|
||||||
{
|
|
||||||
PackageName = node.IdPackage;
|
|
||||||
}
|
|
||||||
if (string.IsNullOrWhiteSpace(Website) && !string.IsNullOrWhiteSpace(node.WebDomain))
|
|
||||||
{
|
|
||||||
var scheme = "http";
|
|
||||||
if ((int)Build.VERSION.SdkInt >= 28)
|
|
||||||
{
|
|
||||||
scheme = node.WebScheme;
|
|
||||||
}
|
|
||||||
Website = string.Format("{0}://{1}", scheme, node.WebDomain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetTitlePackageId(WindowNode node)
|
|
||||||
{
|
|
||||||
if (node != null && !string.IsNullOrWhiteSpace(node.Title))
|
|
||||||
{
|
|
||||||
var slashPosition = node.Title.IndexOf('/');
|
|
||||||
if (slashPosition > -1)
|
|
||||||
{
|
|
||||||
var packageId = node.Title.Substring(0, slashPosition);
|
|
||||||
if (packageId.Contains("."))
|
|
||||||
{
|
|
||||||
return packageId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using Bit.Core.Enums;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Autofill
|
|
||||||
{
|
|
||||||
public class SavedItem
|
|
||||||
{
|
|
||||||
public CipherType Type { get; set; }
|
|
||||||
public LoginItem Login { get; set; }
|
|
||||||
public CardItem Card { get; set; }
|
|
||||||
|
|
||||||
public class LoginItem
|
|
||||||
{
|
|
||||||
public string Username { get; set; }
|
|
||||||
public string Password { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CardItem
|
|
||||||
{
|
|
||||||
public string Name { get; set; }
|
|
||||||
public string Number { get; set; }
|
|
||||||
public string ExpMonth { get; set; }
|
|
||||||
public string ExpYear { get; set; }
|
|
||||||
public string Code { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,30 +3,29 @@ using Android.Content;
|
|||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
using Android.Views;
|
using Android.Views;
|
||||||
using System;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Accessibility
|
namespace Bit.Android
|
||||||
{
|
{
|
||||||
[Activity(Theme = "@style/BaseTheme", WindowSoftInputMode = SoftInput.StateHidden)]
|
[Activity(Theme = "@style/BitwardenTheme.Splash",
|
||||||
public class AccessibilityActivity : Activity
|
Label = "bitwarden",
|
||||||
|
Icon = "@drawable/icon",
|
||||||
|
WindowSoftInputMode = SoftInput.StateHidden)]
|
||||||
|
public class AutofillActivity : Activity
|
||||||
{
|
{
|
||||||
private DateTime? _lastLaunch = null;
|
|
||||||
private string _lastQueriedUri;
|
private string _lastQueriedUri;
|
||||||
|
|
||||||
|
public static AutofillCredentials LastCredentials { get; set; }
|
||||||
|
|
||||||
protected override void OnCreate(Bundle bundle)
|
protected override void OnCreate(Bundle bundle)
|
||||||
{
|
{
|
||||||
Intent?.Validate();
|
|
||||||
base.OnCreate(bundle);
|
base.OnCreate(bundle);
|
||||||
HandleIntent(Intent, 932473);
|
LaunchMainActivity(Intent, 932473);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNewIntent(Intent intent)
|
protected override void OnNewIntent(Intent intent)
|
||||||
{
|
{
|
||||||
base.OnNewIntent(intent);
|
base.OnNewIntent(intent);
|
||||||
HandleIntent(intent, 489729);
|
LaunchMainActivity(intent, 489729);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDestroy()
|
protected override void OnDestroy()
|
||||||
@@ -37,35 +36,37 @@ namespace Bit.Droid.Accessibility
|
|||||||
protected override void OnResume()
|
protected override void OnResume()
|
||||||
{
|
{
|
||||||
base.OnResume();
|
base.OnResume();
|
||||||
if (!Intent.HasExtra("uri"))
|
if(!Intent.HasExtra("uri"))
|
||||||
{
|
{
|
||||||
Finish();
|
Finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent.RemoveExtra("uri");
|
Intent.RemoveExtra("uri");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
|
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
|
||||||
{
|
{
|
||||||
base.OnActivityResult(requestCode, resultCode, data);
|
base.OnActivityResult(requestCode, resultCode, data);
|
||||||
if (data == null)
|
if(data == null)
|
||||||
{
|
{
|
||||||
AccessibilityHelpers.LastCredentials = null;
|
LastCredentials = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (data.GetStringExtra("canceled") != null)
|
if(data.GetStringExtra("canceled") != null)
|
||||||
{
|
{
|
||||||
AccessibilityHelpers.LastCredentials = null;
|
LastCredentials = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var uri = data.GetStringExtra("uri");
|
var uri = data.GetStringExtra("uri");
|
||||||
var username = data.GetStringExtra("username");
|
var username = data.GetStringExtra("username");
|
||||||
var password = data.GetStringExtra("password");
|
var password = data.GetStringExtra("password");
|
||||||
AccessibilityHelpers.LastCredentials = new Credentials
|
|
||||||
|
LastCredentials = new AutofillCredentials
|
||||||
{
|
{
|
||||||
Username = username,
|
Username = username,
|
||||||
Password = password,
|
Password = password,
|
||||||
@@ -76,44 +77,24 @@ namespace Bit.Droid.Accessibility
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
AccessibilityHelpers.LastCredentials = null;
|
LastCredentials = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Finish();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleIntent(Intent callingIntent, int requestCode)
|
Finish();
|
||||||
{
|
|
||||||
if (callingIntent?.GetBooleanExtra("autofillTileClicked", false) ?? false)
|
|
||||||
{
|
|
||||||
Intent.RemoveExtra("autofillTileClicked");
|
|
||||||
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
|
||||||
messagingService.Send("OnAutofillTileClick");
|
|
||||||
Finish();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LaunchMainActivity(callingIntent, requestCode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LaunchMainActivity(Intent callingIntent, int requestCode)
|
private void LaunchMainActivity(Intent callingIntent, int requestCode)
|
||||||
{
|
{
|
||||||
_lastQueriedUri = callingIntent?.GetStringExtra("uri");
|
_lastQueriedUri = callingIntent?.GetStringExtra("uri");
|
||||||
if (_lastQueriedUri == null)
|
if(_lastQueriedUri == null)
|
||||||
{
|
{
|
||||||
Finish();
|
Finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var now = DateTime.UtcNow;
|
|
||||||
if (_lastLaunch.HasValue && (now - _lastLaunch.Value) <= TimeSpan.FromSeconds(2))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastLaunch = now;
|
|
||||||
var intent = new Intent(this, typeof(MainActivity));
|
var intent = new Intent(this, typeof(MainActivity));
|
||||||
if (!callingIntent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory))
|
if(!callingIntent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory))
|
||||||
{
|
{
|
||||||
intent.PutExtra("uri", _lastQueriedUri);
|
intent.PutExtra("uri", _lastQueriedUri);
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Bit.Droid.Accessibility
|
namespace Bit.Android
|
||||||
{
|
{
|
||||||
public class Credentials
|
public class AutofillCredentials
|
||||||
{
|
{
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
public string Password { get; set; }
|
public string Password { get; set; }
|
||||||
458
src/Android/AutofillService.cs
Normal file
458
src/Android/AutofillService.cs
Normal file
@@ -0,0 +1,458 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Android.AccessibilityServices;
|
||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Android.OS;
|
||||||
|
using Android.Views.Accessibility;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using XLabs.Ioc;
|
||||||
|
|
||||||
|
namespace Bit.Android
|
||||||
|
{
|
||||||
|
[Service(Permission = global::Android.Manifest.Permission.BindAccessibilityService, Label = "bitwarden")]
|
||||||
|
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
|
||||||
|
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
|
||||||
|
public class AutofillService : AccessibilityService
|
||||||
|
{
|
||||||
|
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";
|
||||||
|
|
||||||
|
private static Dictionary<string, Browser> SupportedBrowsers => new List<Browser>
|
||||||
|
{
|
||||||
|
new Browser("com.android.chrome", "url_bar"),
|
||||||
|
new Browser("com.chrome.beta", "url_bar"),
|
||||||
|
new Browser("com.android.browser", "url"),
|
||||||
|
new Browser("com.brave.browser", "url_bar"),
|
||||||
|
new Browser("com.opera.browser", "url_field"),
|
||||||
|
new Browser("com.opera.browser.beta", "url_field"),
|
||||||
|
new Browser("com.opera.mini.native", "url_field"),
|
||||||
|
new Browser("com.chrome.dev", "url_bar"),
|
||||||
|
new Browser("com.chrome.canary", "url_bar"),
|
||||||
|
new Browser("com.google.android.apps.chrome", "url_bar"),
|
||||||
|
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
||||||
|
new Browser("org.codeaurora.swe.browser", "url_bar"),
|
||||||
|
new Browser("org.iron.srware", "url_bar"),
|
||||||
|
new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"),
|
||||||
|
new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"),
|
||||||
|
new Browser("com.yandex.browser", "bro_omnibar_address_title_text",
|
||||||
|
(s) => s.Split(' ').FirstOrDefault()),
|
||||||
|
new Browser("org.mozilla.firefox", "url_bar_title"),
|
||||||
|
new Browser("org.mozilla.firefox_beta", "url_bar_title"),
|
||||||
|
new Browser("org.mozilla.focus", "display_url"),
|
||||||
|
new Browser("com.ghostery.android.ghostery", "search_field"),
|
||||||
|
new Browser("org.adblockplus.browser", "url_bar_title"),
|
||||||
|
new Browser("com.htc.sense.browser", "title"),
|
||||||
|
new Browser("com.amazon.cloud9", "url"),
|
||||||
|
new Browser("mobi.mgeek.TunnyBrowser", "title"),
|
||||||
|
new Browser("com.nubelacorp.javelin", "enterUrl"),
|
||||||
|
new Browser("com.jerky.browser2", "enterUrl"),
|
||||||
|
new Browser("com.mx.browser", "address_editor_with_progress"),
|
||||||
|
new Browser("com.mx.browser.tablet", "address_editor_with_progress"),
|
||||||
|
new Browser("com.linkbubble.playstore", "url_text"),
|
||||||
|
new Browser("com.ksmobile.cb", "address_bar_edit_text"),
|
||||||
|
new Browser("acr.browser.lightning", "search"),
|
||||||
|
new Browser("acr.browser.barebones", "search")
|
||||||
|
}.ToDictionary(n => n.PackageName);
|
||||||
|
|
||||||
|
private readonly IAppSettingsService _appSettings;
|
||||||
|
private long _lastNotificationTime = 0;
|
||||||
|
private string _lastNotificationUri = null;
|
||||||
|
|
||||||
|
public AutofillService()
|
||||||
|
{
|
||||||
|
_appSettings = Resolver.Resolve<IAppSettingsService>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
||||||
|
{
|
||||||
|
var powerManager = (PowerManager)GetSystemService(PowerService);
|
||||||
|
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch && !powerManager.IsInteractive)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if(Build.VERSION.SdkInt < BuildVersionCodes.Lollipop && !powerManager.IsScreenOn)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var root = RootInActiveWindow;
|
||||||
|
if(e == null || root == null || string.IsNullOrWhiteSpace(e.PackageName) ||
|
||||||
|
e.PackageName == SystemUiPackage || e.PackageName.Contains("launcher") ||
|
||||||
|
root.PackageName != e.PackageName)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
|
||||||
|
//var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
|
||||||
|
//testNodes.Dispose();
|
||||||
|
|
||||||
|
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
||||||
|
var cancelNotification = true;
|
||||||
|
|
||||||
|
switch(e.EventType)
|
||||||
|
{
|
||||||
|
case EventTypes.ViewFocused:
|
||||||
|
if(!e.Source.Password || !_appSettings.AutofillPasswordField)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.PackageName == BitwardenPackage)
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ScanAndAutofill(root, e, notificationManager, cancelNotification))
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EventTypes.WindowContentChanged:
|
||||||
|
case EventTypes.WindowStateChanged:
|
||||||
|
if(_appSettings.AutofillPasswordField && e.Source.Password)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if(_appSettings.AutofillPasswordField && AutofillActivity.LastCredentials == null)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrWhiteSpace(_lastNotificationUri))
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var uri = GetUri(root);
|
||||||
|
if(uri != _lastNotificationUri)
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager);
|
||||||
|
}
|
||||||
|
else if(uri.StartsWith(App.Constants.AndroidAppProtocol))
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(e.PackageName == BitwardenPackage)
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_appSettings.AutofillPersistNotification)
|
||||||
|
{
|
||||||
|
var uri = GetUri(root);
|
||||||
|
if(uri != null && !uri.Contains(BitwardenWebsite))
|
||||||
|
{
|
||||||
|
var needToFill = NeedToAutofill(AutofillActivity.LastCredentials, uri);
|
||||||
|
if(needToFill)
|
||||||
|
{
|
||||||
|
var passwordNodes = GetWindowNodes(root, e, n => n.Password, false);
|
||||||
|
needToFill = passwordNodes.Any();
|
||||||
|
if(needToFill)
|
||||||
|
{
|
||||||
|
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
|
||||||
|
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||||
|
FillCredentials(usernameEditText, passwordNodes);
|
||||||
|
|
||||||
|
allEditTexts.Dispose();
|
||||||
|
usernameEditText.Dispose();
|
||||||
|
}
|
||||||
|
passwordNodes.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!needToFill)
|
||||||
|
{
|
||||||
|
NotifyToAutofill(uri, notificationManager);
|
||||||
|
cancelNotification = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AutofillActivity.LastCredentials = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cancelNotification = ScanAndAutofill(root, e, notificationManager, cancelNotification);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cancelNotification)
|
||||||
|
{
|
||||||
|
CancelNotification(notificationManager);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationManager?.Dispose();
|
||||||
|
root.Dispose();
|
||||||
|
e.Dispose();
|
||||||
|
}
|
||||||
|
// Suppress exceptions so that service doesn't crash
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInterrupt()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ScanAndAutofill(AccessibilityNodeInfo root, AccessibilityEvent e,
|
||||||
|
NotificationManager notificationManager, bool cancelNotification)
|
||||||
|
{
|
||||||
|
var passwordNodes = GetWindowNodes(root, e, n => n.Password, false);
|
||||||
|
if(passwordNodes.Count > 0)
|
||||||
|
{
|
||||||
|
var uri = GetUri(root);
|
||||||
|
if(uri != null && !uri.Contains(BitwardenWebsite))
|
||||||
|
{
|
||||||
|
if(NeedToAutofill(AutofillActivity.LastCredentials, uri))
|
||||||
|
{
|
||||||
|
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
|
||||||
|
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||||
|
FillCredentials(usernameEditText, passwordNodes);
|
||||||
|
|
||||||
|
allEditTexts.Dispose();
|
||||||
|
usernameEditText.Dispose();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NotifyToAutofill(uri, notificationManager);
|
||||||
|
cancelNotification = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AutofillActivity.LastCredentials = null;
|
||||||
|
}
|
||||||
|
else if(AutofillActivity.LastCredentials != null)
|
||||||
|
{
|
||||||
|
System.Threading.Tasks.Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await System.Threading.Tasks.Task.Delay(1000);
|
||||||
|
AutofillActivity.LastCredentials = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
passwordNodes.Dispose();
|
||||||
|
return cancelNotification;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelNotification(NotificationManager notificationManager, long limit = 250)
|
||||||
|
{
|
||||||
|
if(Java.Lang.JavaSystem.CurrentTimeMillis() - _lastNotificationTime < limit)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastNotificationUri = null;
|
||||||
|
notificationManager?.Cancel(AutoFillNotificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetUri(AccessibilityNodeInfo root)
|
||||||
|
{
|
||||||
|
var uri = string.Concat(App.Constants.AndroidAppProtocol, root.PackageName);
|
||||||
|
if(SupportedBrowsers.ContainsKey(root.PackageName))
|
||||||
|
{
|
||||||
|
var addressNode = root.FindAccessibilityNodeInfosByViewId(
|
||||||
|
$"{root.PackageName}:id/{SupportedBrowsers[root.PackageName].UriViewId}").FirstOrDefault();
|
||||||
|
if(addressNode != null)
|
||||||
|
{
|
||||||
|
uri = ExtractUri(uri, addressNode, SupportedBrowsers[root.PackageName]);
|
||||||
|
addressNode.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser)
|
||||||
|
{
|
||||||
|
if(addressNode?.Text != null)
|
||||||
|
{
|
||||||
|
uri = browser.GetUriFunction(addressNode.Text).Trim();
|
||||||
|
if(uri != null && uri.Contains("."))
|
||||||
|
{
|
||||||
|
if(!uri.Contains("://") && !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 EditText(AccessibilityNodeInfo n)
|
||||||
|
{
|
||||||
|
return n?.ClassName?.Contains("EditText") ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NotifyToAutofill(string uri, NotificationManager notificationManager)
|
||||||
|
{
|
||||||
|
if(notificationManager == null || string.IsNullOrWhiteSpace(uri))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var now = Java.Lang.JavaSystem.CurrentTimeMillis();
|
||||||
|
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 notificationContent = Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch ?
|
||||||
|
App.Resources.AppResources.BitwardenAutofillServiceNotificationContent :
|
||||||
|
App.Resources.AppResources.BitwardenAutofillServiceNotificationContentOld;
|
||||||
|
|
||||||
|
var builder = new Notification.Builder(this);
|
||||||
|
builder.SetSmallIcon(Resource.Drawable.notification_sm)
|
||||||
|
.SetContentTitle(App.Resources.AppResources.BitwardenAutofillService)
|
||||||
|
.SetContentText(notificationContent)
|
||||||
|
.SetTicker(notificationContent)
|
||||||
|
.SetWhen(now)
|
||||||
|
.SetContentIntent(pendingIntent);
|
||||||
|
|
||||||
|
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch)
|
||||||
|
{
|
||||||
|
builder.SetVisibility(NotificationVisibility.Secret)
|
||||||
|
.SetColor(global::Android.Support.V4.Content.ContextCompat.GetColor(ApplicationContext,
|
||||||
|
Resource.Color.primary));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(/*Build.VERSION.SdkInt <= BuildVersionCodes.N && */_appSettings.AutofillPersistNotification)
|
||||||
|
{
|
||||||
|
builder.SetPriority(-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastNotificationTime = now;
|
||||||
|
_lastNotificationUri = uri;
|
||||||
|
notificationManager.Notify(AutoFillNotificationId, builder.Build());
|
||||||
|
|
||||||
|
builder.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FillCredentials(AccessibilityNodeInfo usernameNode, IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
||||||
|
{
|
||||||
|
FillEditText(usernameNode, AutofillActivity.LastCredentials?.Username);
|
||||||
|
foreach(var n in passwordNodes)
|
||||||
|
{
|
||||||
|
FillEditText(n, AutofillActivity.LastCredentials?.Password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillEditText(AccessibilityNodeInfo editTextNode, string value)
|
||||||
|
{
|
||||||
|
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 NodeList GetWindowNodes(AccessibilityNodeInfo n, AccessibilityEvent e,
|
||||||
|
Func<AccessibilityNodeInfo, bool> condition, bool disposeIfUnused, NodeList nodes = null)
|
||||||
|
{
|
||||||
|
if(nodes == null)
|
||||||
|
{
|
||||||
|
nodes = new NodeList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(n != null)
|
||||||
|
{
|
||||||
|
var dispose = disposeIfUnused;
|
||||||
|
if(n.WindowId == e.WindowId && !(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) && condition(n))
|
||||||
|
{
|
||||||
|
dispose = false;
|
||||||
|
nodes.Add(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var i = 0; i < n.ChildCount; i++)
|
||||||
|
{
|
||||||
|
GetWindowNodes(n.GetChild(i), e, condition, true, nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dispose)
|
||||||
|
{
|
||||||
|
n.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Browser
|
||||||
|
{
|
||||||
|
public Browser(string packageName, string uriViewId)
|
||||||
|
{
|
||||||
|
PackageName = packageName;
|
||||||
|
UriViewId = uriViewId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Browser(string packageName, string uriViewId, Func<string, string> getUriFunction)
|
||||||
|
: this(packageName, uriViewId)
|
||||||
|
{
|
||||||
|
GetUriFunction = getUriFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PackageName { get; set; }
|
||||||
|
public string UriViewId { get; set; }
|
||||||
|
public Func<string, string> GetUriFunction { get; set; } = (s) => s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NodeList : List<AccessibilityNodeInfo>, IDisposable
|
||||||
|
{
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach(var item in this)
|
||||||
|
{
|
||||||
|
item.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/Android/Controls/CustomButtonRenderer.cs
Normal file
26
src/Android/Controls/CustomButtonRenderer.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Bit.Android.Controls;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(Button), typeof(CustomButtonRenderer))]
|
||||||
|
namespace Bit.Android.Controls
|
||||||
|
{
|
||||||
|
public class CustomButtonRenderer : ButtonRenderer
|
||||||
|
{
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
if(Control.TextSize == (float)Device.GetNamedSize(NamedSize.Default, typeof(Button)))
|
||||||
|
{
|
||||||
|
Control.TextSize = (float)Device.GetNamedSize(NamedSize.Medium, typeof(Button));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnElementPropertyChanged(sender, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/Android/Controls/CustomSearchBarRenderer.cs
Normal file
18
src/Android/Controls/CustomSearchBarRenderer.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Bit.Android.Controls;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))]
|
||||||
|
namespace Bit.Android.Controls
|
||||||
|
{
|
||||||
|
public class CustomSearchBarRenderer : SearchBarRenderer
|
||||||
|
{
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
Control.SetPadding((int)global::Android.App.Application.Context.ToPixels(-8), 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
src/Android/Controls/ExtendedButtonRenderer.cs
Normal file
62
src/Android/Controls/ExtendedButtonRenderer.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Bit.Android.Controls;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ExtendedButton), typeof(ExtendedButtonRenderer))]
|
||||||
|
namespace Bit.Android.Controls
|
||||||
|
{
|
||||||
|
public class ExtendedButtonRenderer : CustomButtonRenderer
|
||||||
|
{
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
SetPadding();
|
||||||
|
SetUppercase();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnElementPropertyChanged(sender, e);
|
||||||
|
if(e.PropertyName == ExtendedButton.PaddingProperty.PropertyName)
|
||||||
|
{
|
||||||
|
SetPadding();
|
||||||
|
}
|
||||||
|
else if(e.PropertyName == ExtendedButton.UppercaseProperty.PropertyName)
|
||||||
|
{
|
||||||
|
SetUppercase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetPadding()
|
||||||
|
{
|
||||||
|
var element = Element as ExtendedButton;
|
||||||
|
if(element != null)
|
||||||
|
{
|
||||||
|
Control.SetPadding(
|
||||||
|
(int)element.Padding.Left,
|
||||||
|
(int)element.Padding.Top,
|
||||||
|
(int)element.Padding.Right,
|
||||||
|
(int)element.Padding.Bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetUppercase()
|
||||||
|
{
|
||||||
|
var element = Element as ExtendedButton;
|
||||||
|
if(element != null && !string.IsNullOrWhiteSpace(element.Text))
|
||||||
|
{
|
||||||
|
if(element.Uppercase)
|
||||||
|
{
|
||||||
|
element.Text = element.Text.ToUpperInvariant();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Control.TransformationMethod = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
src/Android/Controls/ExtendedEditorRenderer.cs
Normal file
81
src/Android/Controls/ExtendedEditorRenderer.cs
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Bit.Android.Controls;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using Android.Text.Method;
|
||||||
|
using Android.Views;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ExtendedEditor), typeof(ExtendedEditorRenderer))]
|
||||||
|
namespace Bit.Android.Controls
|
||||||
|
{
|
||||||
|
public class ExtendedEditorRenderer : EditorRenderer
|
||||||
|
{
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
|
var view = (ExtendedEditor)Element;
|
||||||
|
|
||||||
|
SetBorder(view);
|
||||||
|
SetScrollable();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var view = (ExtendedEditor)Element;
|
||||||
|
|
||||||
|
if(e.PropertyName == ExtendedEditor.HasBorderProperty.PropertyName)
|
||||||
|
{
|
||||||
|
SetBorder(view);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base.OnElementPropertyChanged(sender, e);
|
||||||
|
if(e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
Control.SetBackgroundColor(view.BackgroundColor.ToAndroid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetBorder(ExtendedEditor view)
|
||||||
|
{
|
||||||
|
if(!view.HasBorder)
|
||||||
|
{
|
||||||
|
Control.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetScrollable()
|
||||||
|
{
|
||||||
|
// While scrolling inside Editor stop scrolling parent view.
|
||||||
|
Control.OverScrollMode = OverScrollMode.Always;
|
||||||
|
Control.ScrollBarStyle = ScrollbarStyles.InsideInset;
|
||||||
|
Control.SetOnTouchListener(new EditorTouchListener());
|
||||||
|
|
||||||
|
// For Scrolling in Editor innner area
|
||||||
|
Control.VerticalScrollBarEnabled = true;
|
||||||
|
Control.ScrollBarStyle = ScrollbarStyles.InsideInset;
|
||||||
|
|
||||||
|
// Force scrollbars to be displayed
|
||||||
|
var arr = Control.Context.Theme.ObtainStyledAttributes(new int[0]);
|
||||||
|
InitializeScrollbars(arr);
|
||||||
|
arr.Recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EditorTouchListener : Java.Lang.Object, IOnTouchListener
|
||||||
|
{
|
||||||
|
public bool OnTouch(global::Android.Views.View v, MotionEvent e)
|
||||||
|
{
|
||||||
|
v.Parent?.RequestDisallowInterceptTouchEvent(true);
|
||||||
|
if((e.Action & MotionEventActions.Up) != 0 && (e.ActionMasked & MotionEventActions.Up) != 0)
|
||||||
|
{
|
||||||
|
v.Parent?.RequestDisallowInterceptTouchEvent(false);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
201
src/Android/Controls/ExtendedEntryRenderer.cs
Normal file
201
src/Android/Controls/ExtendedEntryRenderer.cs
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Android.Graphics;
|
||||||
|
using Android.Text;
|
||||||
|
using Android.Text.Method;
|
||||||
|
using Android.Views.InputMethods;
|
||||||
|
using Android.Widget;
|
||||||
|
using Bit.Android.Controls;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Bit.App.Enums;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ExtendedEntry), typeof(ExtendedEntryRenderer))]
|
||||||
|
namespace Bit.Android.Controls
|
||||||
|
{
|
||||||
|
public class ExtendedEntryRenderer : EntryRenderer
|
||||||
|
{
|
||||||
|
private bool _isPassword;
|
||||||
|
private bool _toggledPassword;
|
||||||
|
private bool _isDisposed;
|
||||||
|
private ExtendedEntry _view;
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
|
_view = (ExtendedEntry)Element;
|
||||||
|
_isPassword = _view.IsPassword;
|
||||||
|
|
||||||
|
if(Control != null)
|
||||||
|
{
|
||||||
|
Control.SetIncludeFontPadding(false);
|
||||||
|
if(e.NewElement != null && e.NewElement.IsPassword)
|
||||||
|
{
|
||||||
|
Control.SetTypeface(Typeface.Default, TypefaceStyle.Normal);
|
||||||
|
Control.TransformationMethod = new PasswordTransformationMethod();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SetBorder(_view);
|
||||||
|
SetMaxLength(_view);
|
||||||
|
SetReturnType(_view);
|
||||||
|
|
||||||
|
// Editor Action is called when the return button is pressed
|
||||||
|
Control.EditorAction += Control_EditorAction;
|
||||||
|
|
||||||
|
if(_view.DisableAutocapitalize)
|
||||||
|
{
|
||||||
|
Control.SetRawInputType(Control.InputType |= InputTypes.TextVariationEmailAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_view.Autocorrect.HasValue)
|
||||||
|
{
|
||||||
|
Control.SetRawInputType(Control.InputType |= InputTypes.TextFlagNoSuggestions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_view.IsPassword)
|
||||||
|
{
|
||||||
|
Control.SetRawInputType(InputTypes.TextFlagNoSuggestions | InputTypes.TextVariationVisiblePassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
_view.ToggleIsPassword += ToggleIsPassword;
|
||||||
|
|
||||||
|
if(_view.FontFamily == "monospace")
|
||||||
|
{
|
||||||
|
Control.Typeface = Typeface.Monospace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleIsPassword(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var cursorStart = Control.SelectionStart;
|
||||||
|
var cursorEnd = Control.SelectionEnd;
|
||||||
|
|
||||||
|
Control.TransformationMethod = _isPassword ? null : new PasswordTransformationMethod();
|
||||||
|
Control.SetRawInputType(InputTypes.TextFlagNoSuggestions | InputTypes.TextVariationVisiblePassword);
|
||||||
|
|
||||||
|
// set focus
|
||||||
|
Control.RequestFocus();
|
||||||
|
|
||||||
|
if(_toggledPassword)
|
||||||
|
{
|
||||||
|
// restore cursor position
|
||||||
|
Control.SetSelection(cursorStart, cursorEnd);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// set cursor to end
|
||||||
|
Control.SetSelection(Control.Text.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// show keyboard
|
||||||
|
var imm = Forms.Context.GetSystemService(global::Android.Content.Context.InputMethodService) as InputMethodManager;
|
||||||
|
imm.ShowSoftInput(Control, ShowFlags.Forced);
|
||||||
|
imm.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
|
||||||
|
|
||||||
|
_isPassword = _view.IsPasswordFromToggled = !_isPassword;
|
||||||
|
_toggledPassword = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Control_EditorAction(object sender, TextView.EditorActionEventArgs e)
|
||||||
|
{
|
||||||
|
if(_view.ReturnType != ReturnType.Next)
|
||||||
|
{
|
||||||
|
_view.Unfocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call all the methods attached to base_entry event handler Completed
|
||||||
|
_view.InvokeCompleted();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var view = (ExtendedEntry)Element;
|
||||||
|
|
||||||
|
if(e.PropertyName == ExtendedEntry.HasBorderProperty.PropertyName
|
||||||
|
|| e.PropertyName == ExtendedEntry.HasOnlyBottomBorderProperty.PropertyName
|
||||||
|
|| e.PropertyName == ExtendedEntry.BottomBorderColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
SetBorder(view);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base.OnElementPropertyChanged(sender, e);
|
||||||
|
if(e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
Control.SetBackgroundColor(view.BackgroundColor.ToAndroid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(view.FontFamily == "monospace")
|
||||||
|
{
|
||||||
|
Control.Typeface = Typeface.Monospace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if(_isDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
if(disposing && Control != null)
|
||||||
|
{
|
||||||
|
_view.ToggleIsPassword -= ToggleIsPassword;
|
||||||
|
Control.EditorAction -= Control_EditorAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetReturnType(ExtendedEntry view)
|
||||||
|
{
|
||||||
|
if(view.ReturnType.HasValue)
|
||||||
|
{
|
||||||
|
switch(view.ReturnType.Value)
|
||||||
|
{
|
||||||
|
case ReturnType.Go:
|
||||||
|
Control.ImeOptions = ImeAction.Go;
|
||||||
|
Control.SetImeActionLabel("Go", ImeAction.Go);
|
||||||
|
break;
|
||||||
|
case ReturnType.Next:
|
||||||
|
Control.ImeOptions = ImeAction.Next;
|
||||||
|
Control.SetImeActionLabel("Next", ImeAction.Next);
|
||||||
|
break;
|
||||||
|
case ReturnType.Search:
|
||||||
|
Control.ImeOptions = ImeAction.Search;
|
||||||
|
Control.SetImeActionLabel("Search", ImeAction.Search);
|
||||||
|
break;
|
||||||
|
case ReturnType.Send:
|
||||||
|
Control.ImeOptions = ImeAction.Send;
|
||||||
|
Control.SetImeActionLabel("Send", ImeAction.Send);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Control.SetImeActionLabel("Done", ImeAction.Done);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetBorder(ExtendedEntry view)
|
||||||
|
{
|
||||||
|
if(!view.HasBorder)
|
||||||
|
{
|
||||||
|
Control.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Control.SetBackgroundColor(view.BottomBorderColor.ToAndroid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetMaxLength(ExtendedEntry view)
|
||||||
|
{
|
||||||
|
Control.SetFilters(new IInputFilter[] { new InputFilterLengthFilter(view.MaxLength) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/Android/Controls/ExtendedPickerRenderer.cs
Normal file
49
src/Android/Controls/ExtendedPickerRenderer.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Bit.Android.Controls;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ExtendedPicker), typeof(ExtendedPickerRenderer))]
|
||||||
|
namespace Bit.Android.Controls
|
||||||
|
{
|
||||||
|
public class ExtendedPickerRenderer : PickerRenderer
|
||||||
|
{
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
|
var view = (ExtendedPicker)Element;
|
||||||
|
|
||||||
|
Control.TextSize = (float)Device.GetNamedSize(NamedSize.Medium, typeof(Picker));
|
||||||
|
SetBorder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var view = (ExtendedPicker)Element;
|
||||||
|
|
||||||
|
if(e.PropertyName == ExtendedPicker.HasBorderProperty.PropertyName)
|
||||||
|
{
|
||||||
|
SetBorder(view);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base.OnElementPropertyChanged(sender, e);
|
||||||
|
if(e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
Control.SetBackgroundColor(view.BackgroundColor.ToAndroid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetBorder(ExtendedPicker view)
|
||||||
|
{
|
||||||
|
if(!view.HasBorder)
|
||||||
|
{
|
||||||
|
Control.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/Android/Controls/ExtendedSwitchCellRenderer.cs
Normal file
72
src/Android/Controls/ExtendedSwitchCellRenderer.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
using Android.Content;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Android.Views;
|
||||||
|
using Bit.Android.Controls;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using AView = Android.Views.View;
|
||||||
|
using Android.Widget;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ExtendedSwitchCell), typeof(ExtendedSwitchCellRenderer))]
|
||||||
|
namespace Bit.Android.Controls
|
||||||
|
{
|
||||||
|
public class ExtendedSwitchCellRenderer : SwitchCellRenderer
|
||||||
|
{
|
||||||
|
protected BaseCellView View { get; private set; }
|
||||||
|
|
||||||
|
protected override AView GetCellCore(Cell item, AView convertView, ViewGroup parent, Context context)
|
||||||
|
{
|
||||||
|
var View = base.GetCellCore(item, convertView, parent, context) as SwitchCellView;
|
||||||
|
var extendedCell = (ExtendedSwitchCell)item;
|
||||||
|
|
||||||
|
if(View != null)
|
||||||
|
{
|
||||||
|
if(extendedCell.BackgroundColor != Color.White)
|
||||||
|
{
|
||||||
|
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
View.SetBackgroundResource(Resource.Drawable.list_selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(item.IsEnabled)
|
||||||
|
{
|
||||||
|
View.SetMainTextColor(Color.Black);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
View.SetMainTextColor(Color.FromHex("777777"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(View.ChildCount > 1)
|
||||||
|
{
|
||||||
|
var layout = View.GetChildAt(1) as LinearLayout;
|
||||||
|
if(layout != null && layout.ChildCount > 0)
|
||||||
|
{
|
||||||
|
var textView = layout.GetChildAt(0) as TextView;
|
||||||
|
if(textView != null)
|
||||||
|
{
|
||||||
|
textView.TextSize = (float)Device.GetNamedSize(NamedSize.Medium, typeof(Label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return View;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
|
||||||
|
{
|
||||||
|
base.OnCellPropertyChanged(sender, args);
|
||||||
|
|
||||||
|
var cell = (ExtendedSwitchCell)Cell;
|
||||||
|
|
||||||
|
if(args.PropertyName == ExtendedSwitchCell.BackgroundColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
View.SetBackgroundColor(cell.BackgroundColor.ToAndroid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
src/Android/Controls/ExtendedTabbedPageRenderer.cs
Normal file
95
src/Android/Controls/ExtendedTabbedPageRenderer.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using Bit.Android.Controls;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using Android.Support.Design.Widget;
|
||||||
|
using Xamarin.Forms.Platform.Android.AppCompat;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ExtendedTabbedPage), typeof(ExtendedTabbedPageRenderer))]
|
||||||
|
namespace Bit.Android.Controls
|
||||||
|
{
|
||||||
|
public class ExtendedTabbedPageRenderer : TabbedPageRenderer, TabLayout.IOnTabSelectedListener
|
||||||
|
{
|
||||||
|
private TabLayout _tabLayout;
|
||||||
|
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
var view = (ExtendedTabbedPage)Element;
|
||||||
|
|
||||||
|
var field = typeof(ExtendedTabbedPageRenderer).BaseType.GetField("_tabLayout",
|
||||||
|
BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
_tabLayout = field?.GetValue(this) as TabLayout;
|
||||||
|
if(_tabLayout != null)
|
||||||
|
{
|
||||||
|
var tab = _tabLayout.GetTabAt(0);
|
||||||
|
SetSelectedTabIcon(tab, Element.Children[0].Icon, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabLayout.IOnTabSelectedListener.OnTabSelected(TabLayout.Tab tab)
|
||||||
|
{
|
||||||
|
if(Element == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedIndex = tab.Position;
|
||||||
|
var child = Element.Children[selectedIndex];
|
||||||
|
if(Element.Children.Count > selectedIndex && selectedIndex >= 0)
|
||||||
|
{
|
||||||
|
Element.CurrentPage = Element.Children[selectedIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
SetSelectedTabIcon(tab, child.Icon, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabLayout.IOnTabSelectedListener.OnTabUnselected(TabLayout.Tab tab)
|
||||||
|
{
|
||||||
|
var child = Element.Children[tab.Position];
|
||||||
|
SetSelectedTabIcon(tab, child.Icon, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetSelectedTabIcon(TabLayout.Tab tab, string icon, bool selected)
|
||||||
|
{
|
||||||
|
if(string.IsNullOrEmpty(icon))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(selected)
|
||||||
|
{
|
||||||
|
var selectedResource = IdFromTitle(string.Format("{0}_selected", icon), ResourceManager.DrawableClass);
|
||||||
|
if(selectedResource != 0)
|
||||||
|
{
|
||||||
|
tab.SetIcon(selectedResource);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var resource = IdFromTitle(icon, ResourceManager.DrawableClass);
|
||||||
|
tab.SetIcon(resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int IdFromTitle(string title, Type type)
|
||||||
|
{
|
||||||
|
var name = System.IO.Path.GetFileNameWithoutExtension(title);
|
||||||
|
return GetId(type, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetId(Type type, string propertyName)
|
||||||
|
{
|
||||||
|
var props = type.GetFields();
|
||||||
|
var prop = props.FirstOrDefault(p => p.Name == propertyName);
|
||||||
|
if(prop != null)
|
||||||
|
{
|
||||||
|
return (int)prop.GetValue(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
165
src/Android/Controls/ExtendedTableViewRenderer.cs
Normal file
165
src/Android/Controls/ExtendedTableViewRenderer.cs
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
using System;
|
||||||
|
using Android.Widget;
|
||||||
|
using Bit.Android.Controls;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using Android.Content;
|
||||||
|
using AView = Android.Views.View;
|
||||||
|
using AListView = Android.Widget.ListView;
|
||||||
|
using Android.Views;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ExtendedTableView), typeof(ExtendedTableViewRenderer))]
|
||||||
|
namespace Bit.Android.Controls
|
||||||
|
{
|
||||||
|
public class ExtendedTableViewRenderer : TableViewRenderer
|
||||||
|
{
|
||||||
|
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
|
||||||
|
{
|
||||||
|
base.OnElementChanged(e);
|
||||||
|
Control.Divider = null;
|
||||||
|
Control.DividerHeight = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TableViewModelRenderer GetModelRenderer(AListView listView, TableView view)
|
||||||
|
{
|
||||||
|
return new CustomTableViewModelRenderer(Context, listView, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
|
||||||
|
{
|
||||||
|
var baseSize = base.GetDesiredSize(widthConstraint, heightConstraint);
|
||||||
|
var height = ComputeHeight(Control, Convert.ToInt32(baseSize.Request.Width));
|
||||||
|
return new SizeRequest(new Size(baseSize.Request.Width, height));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ComputeHeight(AListView listView, int width)
|
||||||
|
{
|
||||||
|
var element = Element as ExtendedTableView;
|
||||||
|
|
||||||
|
var adapter = listView.Adapter;
|
||||||
|
var totalHeight = listView.PaddingTop + listView.PaddingBottom;
|
||||||
|
var desiredWidth = MeasureSpec.MakeMeasureSpec(width, MeasureSpecMode.AtMost);
|
||||||
|
for(var i = 0; i < adapter.Count; i++)
|
||||||
|
{
|
||||||
|
if(i == 0 && (element?.NoHeader ?? false))
|
||||||
|
{
|
||||||
|
totalHeight += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var view = adapter.GetView(i, null, listView);
|
||||||
|
view.LayoutParameters = new LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent);
|
||||||
|
view.Measure(desiredWidth, MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified));
|
||||||
|
totalHeight += view.MeasuredHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalHeight + (listView.DividerHeight * (adapter.Count - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CustomTableViewModelRenderer : TableViewModelRenderer
|
||||||
|
{
|
||||||
|
private readonly ExtendedTableView _view;
|
||||||
|
private readonly AListView _listView;
|
||||||
|
|
||||||
|
public CustomTableViewModelRenderer(Context context, AListView listView, TableView view)
|
||||||
|
: base(context, listView, view)
|
||||||
|
{
|
||||||
|
_view = view as ExtendedTableView;
|
||||||
|
_listView = listView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ITableViewController Controller => _view;
|
||||||
|
|
||||||
|
// ref http://bit.ly/2b9cjnQ
|
||||||
|
public override AView GetView(int position, AView convertView, ViewGroup parent)
|
||||||
|
{
|
||||||
|
var baseView = base.GetView(position, convertView, parent);
|
||||||
|
var layout = baseView as LinearLayout;
|
||||||
|
if(layout == null)
|
||||||
|
{
|
||||||
|
return baseView;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isHeader, nextIsHeader;
|
||||||
|
var cell = GetCellForPosition(position, out isHeader, out nextIsHeader);
|
||||||
|
if(layout.ChildCount > 0)
|
||||||
|
{
|
||||||
|
layout.RemoveViewAt(0);
|
||||||
|
var cellView = CellFactory.GetCell(cell, convertView, parent, Context, _view);
|
||||||
|
layout.AddView(cellView, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isHeader)
|
||||||
|
{
|
||||||
|
var textCell = layout.GetChildAt(0) as BaseCellView;
|
||||||
|
if(textCell != null)
|
||||||
|
{
|
||||||
|
if(position == 0 && _view.NoHeader)
|
||||||
|
{
|
||||||
|
textCell.Visibility = ViewStates.Gone;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
textCell.MainText = textCell.MainText?.ToUpperInvariant();
|
||||||
|
textCell.SetMainTextColor(Color.FromHex("777777"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var bline = layout.GetChildAt(1);
|
||||||
|
if(bline != null)
|
||||||
|
{
|
||||||
|
bline.SetBackgroundColor(_view.SeparatorColor.ToAndroid());
|
||||||
|
}
|
||||||
|
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy/pasted from Xamarin source. Invoke via reflection instead maybe?
|
||||||
|
private Cell GetCellForPosition(int position, out bool isHeader, out bool nextIsHeader)
|
||||||
|
{
|
||||||
|
isHeader = false;
|
||||||
|
nextIsHeader = false;
|
||||||
|
|
||||||
|
var model = Controller.Model;
|
||||||
|
var sectionCount = model.GetSectionCount();
|
||||||
|
|
||||||
|
for(var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++)
|
||||||
|
{
|
||||||
|
var size = model.GetRowCount(sectionIndex) + 1;
|
||||||
|
if(position == 0)
|
||||||
|
{
|
||||||
|
isHeader = true;
|
||||||
|
nextIsHeader = size == 0 && sectionIndex < sectionCount - 1;
|
||||||
|
|
||||||
|
var header = model.GetHeaderCell(sectionIndex);
|
||||||
|
Cell resultCell = null;
|
||||||
|
if(header != null)
|
||||||
|
{
|
||||||
|
resultCell = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(resultCell == null)
|
||||||
|
{
|
||||||
|
resultCell = new TextCell { Text = model.GetSectionTitle(sectionIndex) };
|
||||||
|
}
|
||||||
|
|
||||||
|
resultCell.Parent = _view;
|
||||||
|
return resultCell;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(position < size)
|
||||||
|
{
|
||||||
|
nextIsHeader = position == size - 1;
|
||||||
|
return (Cell)model.GetItem(sectionIndex, position - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
position -= size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
155
src/Android/Controls/ExtendedTextCellRenderer.cs
Normal file
155
src/Android/Controls/ExtendedTextCellRenderer.cs
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
using Android.Content;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Android.Views;
|
||||||
|
using Bit.Android.Controls;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using AView = Android.Views.View;
|
||||||
|
using Android.Widget;
|
||||||
|
using Android.Text;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ExtendedTextCell), typeof(ExtendedTextCellRenderer))]
|
||||||
|
namespace Bit.Android.Controls
|
||||||
|
{
|
||||||
|
public class ExtendedTextCellRenderer : TextCellRenderer
|
||||||
|
{
|
||||||
|
protected AView View { get; private set; }
|
||||||
|
|
||||||
|
protected override AView GetCellCore(Cell item, AView convertView, ViewGroup parent, Context context)
|
||||||
|
{
|
||||||
|
var View = (BaseCellView)base.GetCellCore(item, convertView, parent, context);
|
||||||
|
var extendedCell = (ExtendedTextCell)item;
|
||||||
|
|
||||||
|
if(View != null)
|
||||||
|
{
|
||||||
|
if(extendedCell.BackgroundColor != Color.White)
|
||||||
|
{
|
||||||
|
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
View.SetBackgroundResource(Resource.Drawable.list_selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(extendedCell.ShowDisclousure)
|
||||||
|
{
|
||||||
|
var resourceId = Resource.Drawable.ion_chevron_right;
|
||||||
|
if(!string.IsNullOrWhiteSpace(extendedCell.DisclousureImage))
|
||||||
|
{
|
||||||
|
var fileName = System.IO.Path.GetFileNameWithoutExtension(extendedCell.DisclousureImage);
|
||||||
|
resourceId = context.Resources.GetIdentifier(fileName, "drawable", context.PackageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
var image = new DisclosureImage(context, extendedCell);
|
||||||
|
image.SetImageResource(resourceId);
|
||||||
|
image.SetPadding(10, 10, 30, 10);
|
||||||
|
View.SetAccessoryView(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(View.ChildCount > 1)
|
||||||
|
{
|
||||||
|
var layout = View.GetChildAt(1) as LinearLayout;
|
||||||
|
if(layout != null)
|
||||||
|
{
|
||||||
|
if(layout.ChildCount > 0)
|
||||||
|
{
|
||||||
|
var textView = layout.GetChildAt(0) as TextView;
|
||||||
|
if(textView != null)
|
||||||
|
{
|
||||||
|
textView.TextSize = (float)Device.GetNamedSize(NamedSize.Medium, typeof(Label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(layout.ChildCount > 1)
|
||||||
|
{
|
||||||
|
var detailView = layout.GetChildAt(1) as TextView;
|
||||||
|
if(detailView != null)
|
||||||
|
{
|
||||||
|
UpdateLineBreakMode(detailView, extendedCell.DetailLineBreakMode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return View;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
|
||||||
|
{
|
||||||
|
base.OnCellPropertyChanged(sender, args);
|
||||||
|
|
||||||
|
var cell = (ExtendedTextCell)Cell;
|
||||||
|
|
||||||
|
if(args.PropertyName == ExtendedTextCell.BackgroundColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
View.SetBackgroundColor(cell.BackgroundColor.ToAndroid());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: other properties
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateLineBreakMode(TextView view, LineBreakMode lineBreakMode)
|
||||||
|
{
|
||||||
|
if(view == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch(lineBreakMode)
|
||||||
|
{
|
||||||
|
case LineBreakMode.NoWrap:
|
||||||
|
view.SetSingleLine(true);
|
||||||
|
view.Ellipsize = null;
|
||||||
|
break;
|
||||||
|
case LineBreakMode.WordWrap:
|
||||||
|
view.SetSingleLine(false);
|
||||||
|
view.Ellipsize = null;
|
||||||
|
view.SetMaxLines(100);
|
||||||
|
break;
|
||||||
|
case LineBreakMode.CharacterWrap:
|
||||||
|
view.SetSingleLine(false);
|
||||||
|
view.Ellipsize = null;
|
||||||
|
view.SetMaxLines(100);
|
||||||
|
break;
|
||||||
|
case LineBreakMode.HeadTruncation:
|
||||||
|
view.SetSingleLine(true);
|
||||||
|
view.Ellipsize = TextUtils.TruncateAt.Start;
|
||||||
|
break;
|
||||||
|
case LineBreakMode.TailTruncation:
|
||||||
|
view.SetSingleLine(true);
|
||||||
|
view.Ellipsize = TextUtils.TruncateAt.End;
|
||||||
|
break;
|
||||||
|
case LineBreakMode.MiddleTruncation:
|
||||||
|
view.SetSingleLine(true);
|
||||||
|
view.Ellipsize = TextUtils.TruncateAt.Middle;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DisclosureImage : ImageView
|
||||||
|
{
|
||||||
|
private ExtendedTextCell _cell;
|
||||||
|
|
||||||
|
public DisclosureImage(Context context, ExtendedTextCell cell) : base(context)
|
||||||
|
{
|
||||||
|
_cell = cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnTouchEvent(MotionEvent e)
|
||||||
|
{
|
||||||
|
switch(e.Action)
|
||||||
|
{
|
||||||
|
case MotionEventActions.Up:
|
||||||
|
_cell.OnDisclousureTapped();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
49
src/Android/Controls/ExtendedViewCellRenderer.cs
Normal file
49
src/Android/Controls/ExtendedViewCellRenderer.cs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
using Android.Content;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using Android.Views;
|
||||||
|
using Bit.Android.Controls;
|
||||||
|
using Bit.App.Controls;
|
||||||
|
using Xamarin.Forms;
|
||||||
|
using Xamarin.Forms.Platform.Android;
|
||||||
|
using AView = Android.Views.View;
|
||||||
|
|
||||||
|
[assembly: ExportRenderer(typeof(ExtendedViewCell), typeof(ExtendedViewCellRenderer))]
|
||||||
|
namespace Bit.Android.Controls
|
||||||
|
{
|
||||||
|
public class ExtendedViewCellRenderer : ViewCellRenderer
|
||||||
|
{
|
||||||
|
protected AView View { get; private set; }
|
||||||
|
|
||||||
|
protected override AView GetCellCore(Cell item, AView convertView, ViewGroup parent, Context context)
|
||||||
|
{
|
||||||
|
var View = base.GetCellCore(item, convertView, parent, context);
|
||||||
|
var extendedCell = (ExtendedViewCell)item;
|
||||||
|
|
||||||
|
if(View != null)
|
||||||
|
{
|
||||||
|
if(extendedCell.BackgroundColor != Color.White)
|
||||||
|
{
|
||||||
|
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
View.SetBackgroundResource(Resource.Drawable.list_selector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return View;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
|
||||||
|
{
|
||||||
|
base.OnCellPropertyChanged(sender, args);
|
||||||
|
|
||||||
|
var cell = (ExtendedViewCell)Cell;
|
||||||
|
|
||||||
|
if(args.PropertyName == ExtendedViewCell.BackgroundColorProperty.PropertyName)
|
||||||
|
{
|
||||||
|
View.SetBackgroundColor(cell.BackgroundColor.ToAndroid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,59 +1,50 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using Bit.Android.Controls;
|
||||||
using Bit.App.Controls;
|
using Bit.App.Controls;
|
||||||
using Xamarin.Forms;
|
using Xamarin.Forms;
|
||||||
using Xamarin.Forms.Platform.Android;
|
using Xamarin.Forms.Platform.Android;
|
||||||
using Android.Webkit;
|
using Android.Webkit;
|
||||||
using AWebkit = Android.Webkit;
|
using AWebkit = Android.Webkit;
|
||||||
using Java.Interop;
|
using Java.Interop;
|
||||||
using Android.Content;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using System.ComponentModel;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
|
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
|
||||||
namespace Bit.Droid.Renderers
|
namespace Bit.Android.Controls
|
||||||
{
|
{
|
||||||
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, AWebkit.WebView>
|
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, AWebkit.WebView>
|
||||||
{
|
{
|
||||||
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
|
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
|
||||||
|
|
||||||
private readonly Context _context;
|
|
||||||
|
|
||||||
public HybridWebViewRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{
|
|
||||||
_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
|
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
|
||||||
{
|
{
|
||||||
base.OnElementChanged(e);
|
base.OnElementChanged(e);
|
||||||
|
|
||||||
if (Control == null)
|
if(Control == null)
|
||||||
{
|
{
|
||||||
var webView = new AWebkit.WebView(_context);
|
var webView = new AWebkit.WebView(Forms.Context);
|
||||||
webView.Settings.JavaScriptEnabled = true;
|
webView.Settings.JavaScriptEnabled = true;
|
||||||
webView.SetWebViewClient(new JSWebViewClient(string.Format("javascript: {0}", JSFunction)));
|
|
||||||
SetNativeControl(webView);
|
SetNativeControl(webView);
|
||||||
}
|
}
|
||||||
if (e.OldElement != null)
|
|
||||||
|
if(e.OldElement != null)
|
||||||
{
|
{
|
||||||
Control.RemoveJavascriptInterface("jsBridge");
|
Control.RemoveJavascriptInterface("jsBridge");
|
||||||
var hybridWebView = e.OldElement as HybridWebView;
|
var hybridWebView = e.OldElement as HybridWebView;
|
||||||
hybridWebView.Cleanup();
|
hybridWebView.Cleanup();
|
||||||
}
|
}
|
||||||
if (e.NewElement != null)
|
|
||||||
|
if(e.NewElement != null)
|
||||||
{
|
{
|
||||||
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
|
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
|
||||||
Control.LoadUrl(Element.Uri);
|
Control.LoadUrl(Element.Uri);
|
||||||
|
InjectJS(JSFunction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
private void InjectJS(string script)
|
||||||
{
|
{
|
||||||
base.OnElementPropertyChanged(sender, e);
|
if(Control != null)
|
||||||
if (e.PropertyName == HybridWebView.UriProperty.PropertyName)
|
|
||||||
{
|
{
|
||||||
Control.LoadUrl(Element.Uri);
|
Control.LoadUrl(string.Format("javascript: {0}", script));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,29 +61,12 @@ namespace Bit.Droid.Renderers
|
|||||||
[Export("invokeAction")]
|
[Export("invokeAction")]
|
||||||
public void InvokeAction(string data)
|
public void InvokeAction(string data)
|
||||||
{
|
{
|
||||||
if (_hybridWebViewRenderer != null &&
|
HybridWebViewRenderer hybridRenderer;
|
||||||
_hybridWebViewRenderer.TryGetTarget(out HybridWebViewRenderer hybridRenderer))
|
if(_hybridWebViewRenderer != null && _hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
|
||||||
{
|
{
|
||||||
hybridRenderer.Element.InvokeAction(data);
|
hybridRenderer.Element.InvokeAction(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class JSWebViewClient : WebViewClient
|
|
||||||
{
|
|
||||||
private readonly string _javascript;
|
|
||||||
|
|
||||||
public JSWebViewClient(string javascript)
|
|
||||||
{
|
|
||||||
_javascript = javascript;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnPageFinished(AWebkit.WebView view, string url)
|
|
||||||
{
|
|
||||||
base.OnPageFinished(view, url);
|
|
||||||
view.EvaluateJavascript(_javascript, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using Android.Graphics.Drawables;
|
|
||||||
using Bit.Droid.Effects;
|
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportEffect(typeof(FabShadowEffect), "FabShadowEffect")]
|
|
||||||
namespace Bit.Droid.Effects
|
|
||||||
{
|
|
||||||
public class FabShadowEffect : PlatformEffect
|
|
||||||
{
|
|
||||||
protected override void OnAttached ()
|
|
||||||
{
|
|
||||||
if (Control is Android.Widget.Button button)
|
|
||||||
{
|
|
||||||
var gd = new GradientDrawable();
|
|
||||||
gd.SetColor(ThemeHelpers.FabColor);
|
|
||||||
gd.SetCornerRadius(100);
|
|
||||||
|
|
||||||
button.SetBackground(gd);
|
|
||||||
button.Elevation = 6;
|
|
||||||
button.TranslationZ = 20;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDetached ()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using Android.Widget;
|
|
||||||
using Bit.Droid.Effects;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportEffect(typeof(FixedSizeEffect), "FixedSizeEffect")]
|
|
||||||
namespace Bit.Droid.Effects
|
|
||||||
{
|
|
||||||
public class FixedSizeEffect : PlatformEffect
|
|
||||||
{
|
|
||||||
protected override void OnAttached()
|
|
||||||
{
|
|
||||||
if (Element is Label label && Control is TextView textView)
|
|
||||||
{
|
|
||||||
textView.SetTextSize(Android.Util.ComplexUnitType.Pt, (float)label.FontSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDetached()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using Android.Views;
|
|
||||||
using Bit.Droid.Effects;
|
|
||||||
using Google.Android.Material.BottomNavigation;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ResolutionGroupName("Bitwarden")]
|
|
||||||
[assembly: ExportEffect(typeof(TabBarEffect), "TabBarEffect")]
|
|
||||||
namespace Bit.Droid.Effects
|
|
||||||
{
|
|
||||||
public class TabBarEffect : PlatformEffect
|
|
||||||
{
|
|
||||||
protected override void OnAttached()
|
|
||||||
{
|
|
||||||
if (!(Container.GetChildAt(0) is ViewGroup layout))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!(layout.GetChildAt(1) is BottomNavigationView bottomNavigationView))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
bottomNavigationView.LabelVisibilityMode = LabelVisibilityMode.LabelVisibilityLabeled;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDetached()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
54
src/Android/HockeyAppCrashManagerListener.cs
Normal file
54
src/Android/HockeyAppCrashManagerListener.cs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
using HockeyApp.Android;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Android.Runtime;
|
||||||
|
|
||||||
|
namespace Bit.Android
|
||||||
|
{
|
||||||
|
public class HockeyAppCrashManagerListener : CrashManagerListener
|
||||||
|
{
|
||||||
|
private readonly IAppIdService _appIdService;
|
||||||
|
private readonly IAuthService _authService;
|
||||||
|
|
||||||
|
public HockeyAppCrashManagerListener()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public HockeyAppCrashManagerListener(System.IntPtr javaRef, JniHandleOwnership transfer)
|
||||||
|
: base(javaRef, transfer)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public HockeyAppCrashManagerListener(
|
||||||
|
IAppIdService appIdService,
|
||||||
|
IAuthService authService)
|
||||||
|
{
|
||||||
|
_appIdService = appIdService;
|
||||||
|
_authService = authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Description
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if(_appIdService != null && _authService != null)
|
||||||
|
{
|
||||||
|
var log = new
|
||||||
|
{
|
||||||
|
AppId = _appIdService.AppId,
|
||||||
|
UserId = _authService.UserId
|
||||||
|
};
|
||||||
|
|
||||||
|
return JsonConvert.SerializeObject(log, Formatting.Indented);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ShouldAutoUploadCrashes()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,398 +1,380 @@
|
|||||||
using Android.App;
|
using System;
|
||||||
|
using Android.App;
|
||||||
using Android.Content.PM;
|
using Android.Content.PM;
|
||||||
using Android.Runtime;
|
using Android.Views;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Bit.Core;
|
|
||||||
using System.Linq;
|
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.Core.Utilities;
|
using XLabs.Ioc;
|
||||||
using Bit.Core.Abstractions;
|
using Plugin.Settings.Abstractions;
|
||||||
using System.IO;
|
using Plugin.Connectivity.Abstractions;
|
||||||
using System;
|
using Acr.UserDialogs;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Bit.Droid.Utilities;
|
using System.Reflection;
|
||||||
using Bit.Droid.Receivers;
|
using Xamarin.Forms.Platform.Android;
|
||||||
using Bit.App.Models;
|
using Xamarin.Forms;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Android.Nfc;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AndroidX.Core.Content;
|
using Bit.App.Models.Page;
|
||||||
using Bit.App.Utilities;
|
using Bit.App;
|
||||||
using ZXing.Net.Mobile.Android;
|
using Android.Nfc;
|
||||||
using Android.Util;
|
using Android.Views.InputMethods;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Bit.Droid
|
namespace Bit.Android
|
||||||
{
|
{
|
||||||
// Activity and IntentFilter declarations have been moved to Properties/AndroidManifest.xml
|
[Activity(Label = "bitwarden",
|
||||||
// They have been hardcoded so we can use the default LaunchMode on Android 11+
|
Icon = "@drawable/icon",
|
||||||
// LaunchMode defined in values/manifest.xml for Android 10- and values-v30/manifest.xml for Android 11+
|
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
|
||||||
// See https://github.com/bitwarden/mobile/pull/1673 for details
|
public class MainActivity : FormsAppCompatActivity
|
||||||
[Register("com.x8bit.bitwarden.MainActivity")]
|
|
||||||
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
|
|
||||||
{
|
{
|
||||||
|
private const string HockeyAppId = "d3834185b4a643479047b86c65293d42";
|
||||||
|
private DateTime? _lastAction;
|
||||||
|
private Java.Util.Regex.Pattern _otpPattern = Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$");
|
||||||
private IDeviceActionService _deviceActionService;
|
private IDeviceActionService _deviceActionService;
|
||||||
private IMessagingService _messagingService;
|
private ISettings _settings;
|
||||||
private IBroadcasterService _broadcasterService;
|
|
||||||
private IUserService _userService;
|
|
||||||
private IAppIdService _appIdService;
|
|
||||||
private IEventService _eventService;
|
|
||||||
private PendingIntent _eventUploadPendingIntent;
|
|
||||||
private AppOptions _appOptions;
|
|
||||||
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
|
|
||||||
private Java.Util.Regex.Pattern _otpPattern =
|
|
||||||
Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$");
|
|
||||||
|
|
||||||
protected override void OnCreate(Bundle savedInstanceState)
|
protected override void OnCreate(Bundle bundle)
|
||||||
{
|
{
|
||||||
var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver));
|
var uri = Intent.GetStringExtra("uri");
|
||||||
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
|
if(!Resolver.IsSet)
|
||||||
PendingIntentFlags.UpdateCurrent);
|
{
|
||||||
|
MainApplication.SetIoc(Application);
|
||||||
|
}
|
||||||
|
|
||||||
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
|
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
|
||||||
StrictMode.SetThreadPolicy(policy);
|
StrictMode.SetThreadPolicy(policy);
|
||||||
|
|
||||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
ToolbarResource = Resource.Layout.toolbar;
|
||||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
TabLayoutResource = Resource.Layout.tabs;
|
||||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
|
||||||
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
|
||||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
|
||||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
|
||||||
|
|
||||||
TabLayoutResource = Resource.Layout.Tabbar;
|
base.OnCreate(bundle);
|
||||||
ToolbarResource = Resource.Layout.Toolbar;
|
|
||||||
|
|
||||||
// this needs to be called here before base.OnCreate(...)
|
// workaround for app compat bug
|
||||||
Intent?.Validate();
|
// ref https://forums.xamarin.com/discussion/62414/app-resuming-results-in-crash-with-formsappcompatactivity
|
||||||
|
Task.Delay(10).Wait();
|
||||||
|
|
||||||
base.OnCreate(savedInstanceState);
|
Console.WriteLine("A OnCreate");
|
||||||
if (!CoreHelpers.InDebugMode())
|
if(!App.Utilities.Helpers.InDebugMode())
|
||||||
{
|
{
|
||||||
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
Window.AddFlags(WindowManagerFlags.Secure);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !FDROID
|
var appIdService = Resolver.Resolve<IAppIdService>();
|
||||||
var appCenterHelper = new AppCenterHelper(_appIdService, _userService);
|
var authService = Resolver.Resolve<IAuthService>();
|
||||||
var appCenterTask = appCenterHelper.InitAsync();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
|
HockeyApp.Android.CrashManager.Register(this, HockeyAppId,
|
||||||
Xamarin.Forms.Forms.Init(this, savedInstanceState);
|
new HockeyAppCrashManagerListener(appIdService, authService));
|
||||||
_appOptions = GetOptions();
|
|
||||||
LoadApplication(new App.App(_appOptions));
|
|
||||||
|
|
||||||
_broadcasterService.Subscribe(_activityKey, (message) =>
|
Forms.Init(this, bundle);
|
||||||
|
|
||||||
|
typeof(Color).GetProperty("Accent", BindingFlags.Public | BindingFlags.Static)
|
||||||
|
.SetValue(null, Color.FromHex("d2d6de"));
|
||||||
|
|
||||||
|
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||||
|
_settings = Resolver.Resolve<ISettings>();
|
||||||
|
LoadApplication(new App.App(
|
||||||
|
uri,
|
||||||
|
Intent.GetBooleanExtra("myVaultTile", false),
|
||||||
|
Resolver.Resolve<IAuthService>(),
|
||||||
|
Resolver.Resolve<IConnectivity>(),
|
||||||
|
Resolver.Resolve<IUserDialogs>(),
|
||||||
|
Resolver.Resolve<IDatabaseService>(),
|
||||||
|
Resolver.Resolve<ISyncService>(),
|
||||||
|
_settings,
|
||||||
|
Resolver.Resolve<ILockService>(),
|
||||||
|
Resolver.Resolve<IGoogleAnalyticsService>(),
|
||||||
|
Resolver.Resolve<ILocalizeService>(),
|
||||||
|
Resolver.Resolve<IAppInfoService>(),
|
||||||
|
Resolver.Resolve<IAppSettingsService>(),
|
||||||
|
_deviceActionService));
|
||||||
|
|
||||||
|
MessagingCenter.Subscribe<Xamarin.Forms.Application>(
|
||||||
|
Xamarin.Forms.Application.Current, "DismissKeyboard", (sender) =>
|
||||||
{
|
{
|
||||||
if (message.Command == "startEventTimer")
|
DismissKeyboard();
|
||||||
{
|
|
||||||
StartEventAlarm();
|
|
||||||
}
|
|
||||||
else if (message.Command == "stopEventTimer")
|
|
||||||
{
|
|
||||||
var task = StopEventAlarmAsync();
|
|
||||||
}
|
|
||||||
else if (message.Command == "finishMainActivity")
|
|
||||||
{
|
|
||||||
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => Finish());
|
|
||||||
}
|
|
||||||
else if (message.Command == "listenYubiKeyOTP")
|
|
||||||
{
|
|
||||||
ListenYubiKey((bool)message.Data);
|
|
||||||
}
|
|
||||||
else if (message.Command == "updatedTheme")
|
|
||||||
{
|
|
||||||
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => AppearanceAdjustments());
|
|
||||||
}
|
|
||||||
else if (message.Command == "exit")
|
|
||||||
{
|
|
||||||
ExitApp();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
MessagingCenter.Subscribe<Xamarin.Forms.Application, string>(
|
||||||
|
Xamarin.Forms.Application.Current, "LaunchApp", (sender, args) =>
|
||||||
|
{
|
||||||
|
LaunchApp(args);
|
||||||
|
});
|
||||||
|
|
||||||
|
MessagingCenter.Subscribe<Xamarin.Forms.Application, bool>(
|
||||||
|
Xamarin.Forms.Application.Current, "ListenYubiKeyOTP", (sender, listen) =>
|
||||||
|
{
|
||||||
|
ListenYubiKey(listen);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReturnCredentials(VaultListPageModel.Login login)
|
||||||
|
{
|
||||||
|
Intent data = new Intent();
|
||||||
|
if(login == null)
|
||||||
|
{
|
||||||
|
data.PutExtra("canceled", "true");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var isPremium = Resolver.Resolve<ITokenService>()?.TokenPremium ?? false;
|
||||||
|
var autoCopyEnabled = !_settings.GetValueOrDefault(Constants.SettingDisableTotpCopy, false);
|
||||||
|
if(isPremium && autoCopyEnabled && _deviceActionService != null && login.Totp.Value != null)
|
||||||
|
{
|
||||||
|
_deviceActionService.CopyToClipboard(App.Utilities.Crypto.Totp(login.Totp.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
protected override void OnPause()
|
||||||
{
|
{
|
||||||
|
Console.WriteLine("A OnPause");
|
||||||
base.OnPause();
|
base.OnPause();
|
||||||
ListenYubiKey(false);
|
ListenYubiKey(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDestroy()
|
||||||
|
{
|
||||||
|
Console.WriteLine("A OnDestroy");
|
||||||
|
base.OnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRestart()
|
||||||
|
{
|
||||||
|
Console.WriteLine("A OnRestart");
|
||||||
|
base.OnRestart();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnStart()
|
||||||
|
{
|
||||||
|
Console.WriteLine("A OnStart");
|
||||||
|
base.OnStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnStop()
|
||||||
|
{
|
||||||
|
Console.WriteLine("A OnStop");
|
||||||
|
base.OnStop();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnResume()
|
protected override void OnResume()
|
||||||
{
|
{
|
||||||
base.OnResume();
|
base.OnResume();
|
||||||
Xamarin.Essentials.Platform.OnResume();
|
Console.WriteLine("A OnResume");
|
||||||
AppearanceAdjustments();
|
|
||||||
if (_deviceActionService.SupportsNfc())
|
// workaround for app compat bug
|
||||||
|
// ref https://bugzilla.xamarin.com/show_bug.cgi?id=36907
|
||||||
|
Task.Delay(10).Wait();
|
||||||
|
|
||||||
|
if(Utilities.NfcEnabled())
|
||||||
{
|
{
|
||||||
try
|
MessagingCenter.Send(Xamarin.Forms.Application.Current, "ResumeYubiKey");
|
||||||
{
|
|
||||||
_messagingService.Send("resumeYubiKey");
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
}
|
||||||
AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this)
|
|
||||||
.GetAwaiter()
|
|
||||||
.GetResult();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnNewIntent(Intent intent)
|
protected override void OnNewIntent(Intent intent)
|
||||||
{
|
{
|
||||||
base.OnNewIntent(intent);
|
base.OnNewIntent(intent);
|
||||||
try
|
Console.WriteLine("A OnNewIntent");
|
||||||
{
|
ParseYubiKey(intent.DataString);
|
||||||
if (intent?.GetStringExtra("uri") is string uri)
|
|
||||||
{
|
|
||||||
_messagingService.Send("popAllAndGoToAutofillCiphers");
|
|
||||||
if (_appOptions != null)
|
|
||||||
{
|
|
||||||
_appOptions.Uri = uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (intent.GetBooleanExtra("generatorTile", false))
|
|
||||||
{
|
|
||||||
_messagingService.Send("popAllAndGoToTabGenerator");
|
|
||||||
if (_appOptions != null)
|
|
||||||
{
|
|
||||||
_appOptions.GeneratorTile = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (intent.GetBooleanExtra("myVaultTile", false))
|
|
||||||
{
|
|
||||||
_messagingService.Send("popAllAndGoToTabMyVault");
|
|
||||||
if (_appOptions != null)
|
|
||||||
{
|
|
||||||
_appOptions.MyVaultTile = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (intent.Action == Intent.ActionSend && intent.Type != null)
|
|
||||||
{
|
|
||||||
if (_appOptions != null)
|
|
||||||
{
|
|
||||||
_appOptions.CreateSend = GetCreateSendRequest(intent);
|
|
||||||
}
|
|
||||||
_messagingService.Send("popAllAndGoToTabSend");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ParseYubiKey(intent.DataString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", e.GetType(), e.StackTrace);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions,
|
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
|
||||||
[GeneratedEnum] Permission[] grantResults)
|
|
||||||
{
|
{
|
||||||
if (requestCode == Constants.SelectFilePermissionRequestCode)
|
if(requestCode == Constants.SelectFilePermissionRequestCode)
|
||||||
{
|
{
|
||||||
if (grantResults.Any(r => r != Permission.Granted))
|
if(grantResults.Any(r => r != Permission.Granted))
|
||||||
{
|
{
|
||||||
_messagingService.Send("selectFileCameraPermissionDenied");
|
MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileCameraPermissionDenied");
|
||||||
}
|
}
|
||||||
await _deviceActionService.SelectFileAsync();
|
await _deviceActionService.SelectFileAsync();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
ZXing.Net.Mobile.Forms.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
}
|
|
||||||
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||||
{
|
{
|
||||||
if (resultCode == Result.Ok &&
|
if(requestCode == Constants.SelectFileRequestCode && resultCode == Result.Ok)
|
||||||
(requestCode == Constants.SelectFileRequestCode || requestCode == Constants.SaveFileRequestCode))
|
|
||||||
{
|
{
|
||||||
Android.Net.Uri uri = null;
|
global::Android.Net.Uri uri = null;
|
||||||
string fileName = null;
|
string fileName = null;
|
||||||
if (data != null && data.Data != null)
|
if(data != null && data.Data != null)
|
||||||
{
|
{
|
||||||
uri = data.Data;
|
uri = data.Data;
|
||||||
fileName = AndroidHelpers.GetFileName(ApplicationContext, uri);
|
fileName = Utilities.GetFileName(ApplicationContext, uri);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// camera
|
// camera
|
||||||
var file = new Java.IO.File(FilesDir, "temp_camera_photo.jpg");
|
var root = new Java.IO.File(global::Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
|
||||||
uri = FileProvider.GetUriForFile(this, "com.x8bit.bitwarden.fileprovider", file);
|
var file = new Java.IO.File(root, "temp_camera_photo.jpg");
|
||||||
|
uri = global::Android.Net.Uri.FromFile(file);
|
||||||
fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
|
fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uri == null)
|
if(uri == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestCode == Constants.SaveFileRequestCode)
|
using(var stream = ContentResolver.OpenInputStream(uri))
|
||||||
|
using(var memoryStream = new MemoryStream())
|
||||||
{
|
{
|
||||||
_messagingService.Send("selectSaveFileResult",
|
stream.CopyTo(memoryStream);
|
||||||
new Tuple<string, string>(uri.ToString(), fileName));
|
MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileResult",
|
||||||
return;
|
new Tuple<byte[], string>(memoryStream.ToArray(), fileName ?? "unknown_file_name"));
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var stream = ContentResolver.OpenInputStream(uri))
|
|
||||||
using (var memoryStream = new MemoryStream())
|
|
||||||
{
|
|
||||||
stream.CopyTo(memoryStream);
|
|
||||||
_messagingService.Send("selectFileResult",
|
|
||||||
new Tuple<byte[], string>(memoryStream.ToArray(), fileName ?? "unknown_file_name"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Java.IO.FileNotFoundException)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDestroy()
|
public void RateApp()
|
||||||
{
|
{
|
||||||
base.OnDestroy();
|
try
|
||||||
_broadcasterService.Unsubscribe(_activityKey);
|
{
|
||||||
|
var rateIntent = RateIntentForUrl("market://details");
|
||||||
|
StartActivity(rateIntent);
|
||||||
|
}
|
||||||
|
catch(ActivityNotFoundException)
|
||||||
|
{
|
||||||
|
var rateIntent = RateIntentForUrl("https://play.google.com/store/apps/details");
|
||||||
|
StartActivity(rateIntent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intent RateIntentForUrl(string url)
|
||||||
|
{
|
||||||
|
var intent = new Intent(Intent.ActionView, global::Android.Net.Uri.Parse($"{url}?id={PackageName}"));
|
||||||
|
var flags = ActivityFlags.NoHistory | ActivityFlags.MultipleTask;
|
||||||
|
if((int)Build.VERSION.SdkInt >= 21)
|
||||||
|
{
|
||||||
|
flags |= ActivityFlags.NewDocument;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// noinspection deprecation
|
||||||
|
flags |= ActivityFlags.ClearWhenTaskReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
intent.AddFlags(flags);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenAccessibilitySettings()
|
||||||
|
{
|
||||||
|
var intent = new Intent(global::Android.Provider.Settings.ActionAccessibilitySettings);
|
||||||
|
StartActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LaunchApp(string packageName)
|
||||||
|
{
|
||||||
|
if(_lastAction.LastActionWasRecent())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_lastAction = DateTime.UtcNow;
|
||||||
|
|
||||||
|
packageName = packageName.Replace("androidapp://", string.Empty);
|
||||||
|
var launchIntent = PackageManager.GetLaunchIntentForPackage(packageName);
|
||||||
|
if(launchIntent == null)
|
||||||
|
{
|
||||||
|
var dialog = Resolver.Resolve<IUserDialogs>();
|
||||||
|
dialog.Alert(string.Format(App.Resources.AppResources.CannotOpenApp, packageName));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StartActivity(launchIntent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ListenYubiKey(bool listen)
|
private void ListenYubiKey(bool listen)
|
||||||
{
|
{
|
||||||
if (!_deviceActionService.SupportsNfc())
|
if(!Utilities.NfcEnabled())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var adapter = NfcAdapter.GetDefaultAdapter(this);
|
var adapter = NfcAdapter.GetDefaultAdapter(this);
|
||||||
if (listen)
|
if(listen)
|
||||||
{
|
{
|
||||||
var intent = new Intent(this, Class);
|
var intent = new Intent(this, Class);
|
||||||
intent.AddFlags(ActivityFlags.SingleTop);
|
intent.AddFlags(ActivityFlags.SingleTop);
|
||||||
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
|
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
|
||||||
|
|
||||||
// register for all NDEF tags starting with http och https
|
// register for all NDEF tags starting with http och https
|
||||||
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
|
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
|
||||||
ndef.AddDataScheme("http");
|
ndef.AddDataScheme("http");
|
||||||
ndef.AddDataScheme("https");
|
ndef.AddDataScheme("https");
|
||||||
var filters = new IntentFilter[] { ndef };
|
var filters = new IntentFilter[] { ndef };
|
||||||
try
|
|
||||||
{
|
// register for foreground dispatch so we'll receive tags according to our intent filters
|
||||||
// register for foreground dispatch so we'll receive tags according to our intent filters
|
adapter.EnableForegroundDispatch(this, pendingIntent, filters, null);
|
||||||
adapter.EnableForegroundDispatch(this, pendingIntent, filters, null);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
try
|
adapter.DisableForegroundDispatch(this);
|
||||||
{
|
|
||||||
adapter.DisableForegroundDispatch(this);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppOptions GetOptions()
|
|
||||||
{
|
|
||||||
var options = new AppOptions
|
|
||||||
{
|
|
||||||
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
|
|
||||||
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
|
|
||||||
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
|
|
||||||
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false),
|
|
||||||
CreateSend = GetCreateSendRequest(Intent)
|
|
||||||
};
|
|
||||||
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
|
|
||||||
if (fillType > 0)
|
|
||||||
{
|
|
||||||
options.FillType = (CipherType)fillType;
|
|
||||||
}
|
|
||||||
if (Intent.GetBooleanExtra("autofillFrameworkSave", false))
|
|
||||||
{
|
|
||||||
options.SaveType = (CipherType)Intent.GetIntExtra("autofillFrameworkType", 0);
|
|
||||||
options.SaveName = Intent.GetStringExtra("autofillFrameworkName");
|
|
||||||
options.SaveUsername = Intent.GetStringExtra("autofillFrameworkUsername");
|
|
||||||
options.SavePassword = Intent.GetStringExtra("autofillFrameworkPassword");
|
|
||||||
options.SaveCardName = Intent.GetStringExtra("autofillFrameworkCardName");
|
|
||||||
options.SaveCardNumber = Intent.GetStringExtra("autofillFrameworkCardNumber");
|
|
||||||
options.SaveCardExpMonth = Intent.GetStringExtra("autofillFrameworkCardExpMonth");
|
|
||||||
options.SaveCardExpYear = Intent.GetStringExtra("autofillFrameworkCardExpYear");
|
|
||||||
options.SaveCardCode = Intent.GetStringExtra("autofillFrameworkCardCode");
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Tuple<SendType, string, byte[], string> GetCreateSendRequest(Intent intent)
|
|
||||||
{
|
|
||||||
if (intent.Action == Intent.ActionSend && intent.Type != null)
|
|
||||||
{
|
|
||||||
if ((intent.Flags & ActivityFlags.LaunchedFromHistory) == ActivityFlags.LaunchedFromHistory)
|
|
||||||
{
|
|
||||||
// don't re-deliver intent if resuming from app switcher
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var type = intent.Type;
|
|
||||||
if (type.Contains("text/"))
|
|
||||||
{
|
|
||||||
var subject = intent.GetStringExtra(Intent.ExtraSubject);
|
|
||||||
var text = intent.GetStringExtra(Intent.ExtraText);
|
|
||||||
return new Tuple<SendType, string, byte[], string>(SendType.Text, subject, null, text);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var data = intent.ClipData?.GetItemAt(0);
|
|
||||||
var uri = data?.Uri;
|
|
||||||
var filename = AndroidHelpers.GetFileName(ApplicationContext, uri);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var stream = ContentResolver.OpenInputStream(uri))
|
|
||||||
using (var memoryStream = new MemoryStream())
|
|
||||||
{
|
|
||||||
stream.CopyTo(memoryStream);
|
|
||||||
return new Tuple<SendType, string, byte[], string>(SendType.File, filename, memoryStream.ToArray(), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Java.IO.FileNotFoundException) { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseYubiKey(string data)
|
private void ParseYubiKey(string data)
|
||||||
{
|
{
|
||||||
if (data == null)
|
if(data == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var otpMatch = _otpPattern.Matcher(data);
|
var otpMatch = _otpPattern.Matcher(data);
|
||||||
if (otpMatch.Matches())
|
if(otpMatch.Matches())
|
||||||
{
|
{
|
||||||
var otp = otpMatch.Group(1);
|
var otp = otpMatch.Group(1);
|
||||||
_messagingService.Send("gotYubiKeyOTP", otp);
|
MessagingCenter.Send(Xamarin.Forms.Application.Current, "GotYubiKeyOTP", otp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AppearanceAdjustments()
|
private void DismissKeyboard()
|
||||||
{
|
{
|
||||||
Window?.SetStatusBarColor(ThemeHelpers.NavBarBackgroundColor);
|
try
|
||||||
Window?.DecorView.SetBackgroundColor(ThemeHelpers.BackgroundColor);
|
{
|
||||||
ThemeHelpers.SetAppearance(ThemeManager.GetTheme(true), ThemeManager.OsDarkModeEnabled());
|
var imm = (InputMethodManager)GetSystemService(InputMethodService);
|
||||||
}
|
imm.HideSoftInputFromWindow(CurrentFocus.WindowToken, 0);
|
||||||
|
}
|
||||||
private void ExitApp()
|
catch { }
|
||||||
{
|
|
||||||
FinishAffinity();
|
|
||||||
Java.Lang.JavaSystem.Exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StartEventAlarm()
|
|
||||||
{
|
|
||||||
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
|
||||||
alarmManager.SetInexactRepeating(AlarmType.ElapsedRealtime, 120000, 300000, _eventUploadPendingIntent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task StopEventAlarmAsync()
|
|
||||||
{
|
|
||||||
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
|
||||||
alarmManager.Cancel(_eventUploadPendingIntent);
|
|
||||||
await _eventService.UploadEventsAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,158 +1,248 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using Acr.UserDialogs;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
using Android.Runtime;
|
using Android.Runtime;
|
||||||
|
using Bit.Android.Services;
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Repositories;
|
||||||
using Bit.App.Services;
|
using Bit.App.Services;
|
||||||
using Bit.Core;
|
using Plugin.Connectivity;
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Bit.Droid.Services;
|
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
using Plugin.CurrentActivity;
|
using Plugin.CurrentActivity;
|
||||||
using Plugin.Fingerprint;
|
using Plugin.Fingerprint;
|
||||||
using Xamarin.Android.Net;
|
using Plugin.Settings;
|
||||||
using System.Net.Http;
|
using PushNotification.Plugin;
|
||||||
using System.Net;
|
using PushNotification.Plugin.Abstractions;
|
||||||
#if !FDROID
|
using XLabs.Ioc;
|
||||||
using Android.Gms.Security;
|
using System.Threading.Tasks;
|
||||||
#endif
|
using Plugin.Settings.Abstractions;
|
||||||
|
using FFImageLoading.Forms.Droid;
|
||||||
|
using XLabs.Ioc.SimpleInjectorContainer;
|
||||||
|
using SimpleInjector;
|
||||||
|
|
||||||
namespace Bit.Droid
|
namespace Bit.Android
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
[Application(Debuggable = true)]
|
[Application(Debuggable = true)]
|
||||||
#else
|
#else
|
||||||
[Application(Debuggable = false)]
|
[Application(Debuggable = false)]
|
||||||
#endif
|
#endif
|
||||||
[Register("com.x8bit.bitwarden.MainApplication")]
|
public class MainApplication : Application, Application.IActivityLifecycleCallbacks
|
||||||
#if FDROID
|
|
||||||
public class MainApplication : Application
|
|
||||||
#else
|
|
||||||
public class MainApplication : Application, ProviderInstaller.IProviderInstallListener
|
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
|
private const string FirstLaunchKey = "firstLaunch";
|
||||||
|
private const string LastVersionCodeKey = "lastVersionCode";
|
||||||
|
|
||||||
|
public static Context AppContext;
|
||||||
|
|
||||||
public MainApplication(IntPtr handle, JniHandleOwnership transer)
|
public MainApplication(IntPtr handle, JniHandleOwnership transer)
|
||||||
: base(handle, transer)
|
: base(handle, transer)
|
||||||
{
|
{
|
||||||
if (ServiceContainer.RegisteredServices.Count == 0)
|
//AndroidEnvironment.UnhandledExceptionRaiser += AndroidEnvironment_UnhandledExceptionRaiser;
|
||||||
|
|
||||||
|
if(!Resolver.IsSet)
|
||||||
{
|
{
|
||||||
RegisterLocalServices();
|
SetIoc(this);
|
||||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
|
||||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
|
|
||||||
Constants.AndroidAllClearCipherCacheKeys);
|
|
||||||
}
|
}
|
||||||
#if !FDROID
|
}
|
||||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
|
||||||
{
|
private void AndroidEnvironment_UnhandledExceptionRaiser(object sender, RaiseThrowableEventArgs e)
|
||||||
ProviderInstaller.InstallIfNeededAsync(ApplicationContext, this);
|
{
|
||||||
}
|
var message = Utilities.AppendExceptionToMessage("", e.Exception);
|
||||||
#endif
|
//Utilities.SaveCrashFile(message, true);
|
||||||
|
Utilities.SendCrashEmail(message, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnCreate()
|
public override void OnCreate()
|
||||||
{
|
{
|
||||||
base.OnCreate();
|
base.OnCreate();
|
||||||
Bootstrap();
|
|
||||||
CrossCurrentActivity.Current.Init(this);
|
// workaround for app compat bug
|
||||||
|
// ref https://forums.xamarin.com/discussion/62414/app-resuming-results-in-crash-with-formsappcompatactivity
|
||||||
|
Task.Delay(10).Wait();
|
||||||
|
|
||||||
|
RegisterActivityLifecycleCallbacks(this);
|
||||||
|
AppContext = ApplicationContext;
|
||||||
|
StartPushService();
|
||||||
|
HandlePushReregistration();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnProviderInstallFailed(int errorCode, Intent recoveryIntent)
|
private void HandlePushReregistration()
|
||||||
{
|
{
|
||||||
}
|
var pushNotification = Resolver.Resolve<IPushNotification>();
|
||||||
|
var settings = Resolver.Resolve<ISettings>();
|
||||||
|
|
||||||
public void OnProviderInstalled()
|
// Reregister for push token based on certain conditions
|
||||||
{
|
// ref https://github.com/rdelrosario/xamarin-plugins/issues/65
|
||||||
}
|
|
||||||
|
|
||||||
private void RegisterLocalServices()
|
var reregister = false;
|
||||||
{
|
|
||||||
ServiceContainer.Register<ILogService>("logService", new AndroidLogService());
|
|
||||||
|
|
||||||
// Note: This might cause a race condition. Investigate more.
|
// 1. First time starting the app after a new install
|
||||||
Task.Run(() =>
|
if(settings.GetValueOrDefault(FirstLaunchKey, true))
|
||||||
{
|
{
|
||||||
FFImageLoading.Forms.Platform.CachedImageRenderer.Init(true);
|
settings.AddOrUpdateValue(FirstLaunchKey, false);
|
||||||
FFImageLoading.ImageService.Instance.Initialize(new FFImageLoading.Config.Configuration
|
reregister = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. App version changed (installed update)
|
||||||
|
var versionCode = Context.ApplicationContext.PackageManager.GetPackageInfo(Context.PackageName, 0).VersionCode;
|
||||||
|
if(settings.GetValueOrDefault(LastVersionCodeKey, -1) != versionCode)
|
||||||
|
{
|
||||||
|
settings.AddOrUpdateValue(LastVersionCodeKey, versionCode);
|
||||||
|
reregister = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. In debug mode
|
||||||
|
if(App.Utilities.Helpers.InDebugMode())
|
||||||
|
{
|
||||||
|
reregister = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Doesn't have a push token currently
|
||||||
|
if(string.IsNullOrWhiteSpace(pushNotification.Token))
|
||||||
|
{
|
||||||
|
reregister = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(reregister)
|
||||||
|
{
|
||||||
|
pushNotification.Unregister();
|
||||||
|
if(Resolver.Resolve<IAuthService>().IsAuthenticated)
|
||||||
{
|
{
|
||||||
FadeAnimationEnabled = false,
|
pushNotification.Register();
|
||||||
FadeAnimationForCachedImages = false,
|
}
|
||||||
HttpClient = new HttpClient(new AndroidClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate })
|
}
|
||||||
});
|
}
|
||||||
ZXing.Net.Mobile.Forms.Android.Platform.Init();
|
|
||||||
});
|
public override void OnTerminate()
|
||||||
|
{
|
||||||
|
base.OnTerminate();
|
||||||
|
UnregisterActivityLifecycleCallbacks(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
|
||||||
|
{
|
||||||
|
CrossCurrentActivity.Current.Activity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnActivityDestroyed(Activity activity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnActivityPaused(Activity activity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnActivityResumed(Activity activity)
|
||||||
|
{
|
||||||
|
CrossCurrentActivity.Current.Activity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnActivityStarted(Activity activity)
|
||||||
|
{
|
||||||
|
CrossCurrentActivity.Current.Activity = activity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnActivityStopped(Activity activity)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StartPushService()
|
||||||
|
{
|
||||||
|
AppContext.StartService(new Intent(AppContext, typeof(PushNotificationService)));
|
||||||
|
if(Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat)
|
||||||
|
{
|
||||||
|
PendingIntent pintent = PendingIntent.GetService(AppContext, 0, new Intent(AppContext,
|
||||||
|
typeof(PushNotificationService)), 0);
|
||||||
|
AlarmManager alarm = (AlarmManager)AppContext.GetSystemService(AlarmService);
|
||||||
|
alarm.Cancel(pintent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void StopPushService()
|
||||||
|
{
|
||||||
|
AppContext.StopService(new Intent(AppContext, typeof(PushNotificationService)));
|
||||||
|
if(Build.VERSION.SdkInt >= BuildVersionCodes.Kitkat)
|
||||||
|
{
|
||||||
|
PendingIntent pintent = PendingIntent.GetService(AppContext, 0, new Intent(AppContext,
|
||||||
|
typeof(PushNotificationService)), 0);
|
||||||
|
AlarmManager alarm = (AlarmManager)AppContext.GetSystemService(AlarmService);
|
||||||
|
alarm.Cancel(pintent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SetIoc(Application application)
|
||||||
|
{
|
||||||
|
UserDialogs.Init(application);
|
||||||
|
CachedImageRenderer.Init();
|
||||||
|
ZXing.Net.Mobile.Forms.Android.Platform.Init();
|
||||||
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
|
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
|
||||||
|
|
||||||
var preferencesStorage = new PreferencesStorageService(null);
|
//var container = new UnityContainer();
|
||||||
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
|
var container = new Container();
|
||||||
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
|
|
||||||
var localizeService = new LocalizeService();
|
|
||||||
var broadcasterService = new BroadcasterService();
|
|
||||||
var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
|
|
||||||
var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo());
|
|
||||||
var secureStorageService = new SecureStorageService();
|
|
||||||
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
|
||||||
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
|
||||||
var deviceActionService = new DeviceActionService(mobileStorageService, messagingService,
|
|
||||||
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
|
|
||||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
|
||||||
broadcasterService);
|
|
||||||
var biometricService = new BiometricService();
|
|
||||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
|
||||||
var cryptoService = new CryptoService(mobileStorageService, secureStorageService, cryptoFunctionService);
|
|
||||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
|
|
||||||
|
|
||||||
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
// Android Stuff
|
||||||
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
|
container.RegisterSingleton(application.ApplicationContext);
|
||||||
ServiceContainer.Register<ILocalizeService>("localizeService", localizeService);
|
container.RegisterSingleton<Application>(application);
|
||||||
ServiceContainer.Register<II18nService>("i18nService", i18nService);
|
|
||||||
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
|
// Services
|
||||||
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
|
container.RegisterSingleton<IDatabaseService, DatabaseService>();
|
||||||
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
container.RegisterSingleton<ISqlService, SqlService>();
|
||||||
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(mobileStorageService));
|
container.RegisterSingleton<ISecureStorageService, AndroidKeyStoreStorageService>();
|
||||||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
container.RegisterSingleton<ICryptoService, CryptoService>();
|
||||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
container.RegisterSingleton<IKeyDerivationService, BouncyCastleKeyDerivationService>();
|
||||||
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
container.RegisterSingleton<IAuthService, AuthService>();
|
||||||
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
container.RegisterSingleton<IFolderService, FolderService>();
|
||||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
container.RegisterSingleton<ILoginService, LoginService>();
|
||||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
container.RegisterSingleton<ISyncService, SyncService>();
|
||||||
|
container.RegisterSingleton<IDeviceActionService, DeviceActionService>();
|
||||||
|
container.RegisterSingleton<IAppIdService, AppIdService>();
|
||||||
|
container.RegisterSingleton<IPasswordGenerationService, PasswordGenerationService>();
|
||||||
|
container.RegisterSingleton<IReflectionService, ReflectionService>();
|
||||||
|
container.RegisterSingleton<ILockService, LockService>();
|
||||||
|
container.RegisterSingleton<IAppInfoService, AppInfoService>();
|
||||||
|
container.RegisterSingleton<IGoogleAnalyticsService, GoogleAnalyticsService>();
|
||||||
|
container.RegisterSingleton<IDeviceInfoService, DeviceInfoService>();
|
||||||
|
container.RegisterSingleton<ILocalizeService, LocalizeService>();
|
||||||
|
container.RegisterSingleton<ILogService, LogService>();
|
||||||
|
container.RegisterSingleton<IHttpService, HttpService>();
|
||||||
|
container.RegisterSingleton<ITokenService, TokenService>();
|
||||||
|
container.RegisterSingleton<ISettingsService, SettingsService>();
|
||||||
|
container.RegisterSingleton<IMemoryService, MemoryService>();
|
||||||
|
container.RegisterSingleton<IAppSettingsService, AppSettingsService>();
|
||||||
|
|
||||||
|
// Repositories
|
||||||
|
container.RegisterSingleton<IFolderRepository, FolderRepository>();
|
||||||
|
container.RegisterSingleton<IFolderApiRepository, FolderApiRepository>();
|
||||||
|
container.RegisterSingleton<ILoginRepository, LoginRepository>();
|
||||||
|
container.RegisterSingleton<IAttachmentRepository, AttachmentRepository>();
|
||||||
|
container.RegisterSingleton<IConnectApiRepository, ConnectApiRepository>();
|
||||||
|
container.RegisterSingleton<IDeviceApiRepository, DeviceApiRepository>();
|
||||||
|
container.RegisterSingleton<IAccountsApiRepository, AccountsApiRepository>();
|
||||||
|
container.RegisterSingleton<ICipherApiRepository, CipherApiRepository>();
|
||||||
|
container.RegisterSingleton<ISettingsRepository, SettingsRepository>();
|
||||||
|
container.RegisterSingleton<ISettingsApiRepository, SettingsApiRepository>();
|
||||||
|
container.RegisterSingleton<ITwoFactorApiRepository, TwoFactorApiRepository>();
|
||||||
|
container.RegisterSingleton<ISyncApiRepository, SyncApiRepository>();
|
||||||
|
|
||||||
|
// Other
|
||||||
|
container.RegisterSingleton(CrossSettings.Current);
|
||||||
|
container.RegisterSingleton(CrossConnectivity.Current);
|
||||||
|
container.RegisterSingleton(UserDialogs.Instance);
|
||||||
|
container.RegisterSingleton(CrossFingerprint.Current);
|
||||||
|
|
||||||
// Push
|
// Push
|
||||||
#if FDROID
|
var pushListener = new PushNotificationListener();
|
||||||
ServiceContainer.Register<IPushNotificationListenerService>(
|
container.RegisterSingleton<IPushNotificationListener>(pushListener);
|
||||||
"pushNotificationListenerService", new NoopPushNotificationListenerService());
|
CrossPushNotification.Initialize(pushListener, "962181367620");
|
||||||
ServiceContainer.Register<IPushNotificationService>(
|
container.RegisterSingleton(CrossPushNotification.Current);
|
||||||
"pushNotificationService", new NoopPushNotificationService());
|
|
||||||
#else
|
|
||||||
var notificationListenerService = new PushNotificationListenerService();
|
|
||||||
ServiceContainer.Register<IPushNotificationListenerService>(
|
|
||||||
"pushNotificationListenerService", notificationListenerService);
|
|
||||||
var androidPushNotificationService = new AndroidPushNotificationService(
|
|
||||||
mobileStorageService, notificationListenerService);
|
|
||||||
ServiceContainer.Register<IPushNotificationService>(
|
|
||||||
"pushNotificationService", androidPushNotificationService);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Bootstrap()
|
container.Verify();
|
||||||
{
|
Resolver.SetResolver(new SimpleInjectorResolver(container));
|
||||||
(ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService).Init();
|
|
||||||
ServiceContainer.Resolve<IAuthService>("authService").Init();
|
|
||||||
// Note: This is not awaited
|
|
||||||
var bootstrapTask = BootstrapAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task BootstrapAsync()
|
|
||||||
{
|
|
||||||
var disableFavicon = await ServiceContainer.Resolve<IStorageService>("storageService")
|
|
||||||
.GetAsync<bool?>(Constants.DisableFaviconKey);
|
|
||||||
await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync(
|
|
||||||
Constants.DisableFaviconKey, disableFavicon);
|
|
||||||
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,13 @@
|
|||||||
using System;
|
using Android.App;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
using Android.Content;
|
||||||
using Android.OS;
|
|
||||||
using Android.Runtime;
|
|
||||||
using Android.Service.QuickSettings;
|
using Android.Service.QuickSettings;
|
||||||
using Android.Views;
|
|
||||||
using Android.Widget;
|
|
||||||
using Java.Lang;
|
using Java.Lang;
|
||||||
|
|
||||||
namespace Bit.Droid.Tile
|
namespace Bit.Android
|
||||||
{
|
{
|
||||||
[Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Label = "@string/MyVault",
|
[Service(Permission = global::Android.Manifest.Permission.BindQuickSettingsTile,
|
||||||
Icon = "@drawable/shield")]
|
Label = "@string/MyVault", Icon = "@drawable/shield")]
|
||||||
[IntentFilter(new string[] { ActionQsTile })]
|
[IntentFilter(new string[] { ActionQsTile })]
|
||||||
[Register("com.x8bit.bitwarden.MyVaultTileService")]
|
|
||||||
public class MyVaultTileService : TileService
|
public class MyVaultTileService : TileService
|
||||||
{
|
{
|
||||||
public override void OnTileAdded()
|
public override void OnTileAdded()
|
||||||
@@ -44,7 +34,7 @@ namespace Bit.Droid.Tile
|
|||||||
{
|
{
|
||||||
base.OnClick();
|
base.OnClick();
|
||||||
|
|
||||||
if (IsLocked)
|
if(IsLocked)
|
||||||
{
|
{
|
||||||
UnlockAndRun(new Runnable(() =>
|
UnlockAndRun(new Runnable(() =>
|
||||||
{
|
{
|
||||||
@@ -59,7 +49,7 @@ namespace Bit.Droid.Tile
|
|||||||
|
|
||||||
private void LaunchMyVault()
|
private void LaunchMyVault()
|
||||||
{
|
{
|
||||||
var intent = new Intent(this, typeof(MainActivity));
|
var intent = new Intent(this, typeof(SplashActivity));
|
||||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||||
intent.PutExtra("myVaultTile", true);
|
intent.PutExtra("myVaultTile", true);
|
||||||
StartActivityAndCollapse(intent);
|
StartActivityAndCollapse(intent);
|
||||||
22
src/Android/PackageReplacedReceiver.cs
Normal file
22
src/Android/PackageReplacedReceiver.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Android.App;
|
||||||
|
using Android.Content;
|
||||||
|
using Bit.App.Abstractions;
|
||||||
|
using Bit.App.Utilities;
|
||||||
|
using Plugin.Settings.Abstractions;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using XLabs.Ioc;
|
||||||
|
|
||||||
|
namespace Bit.Android
|
||||||
|
{
|
||||||
|
[BroadcastReceiver(Name = "com.x8bit.bitwarden.PackageReplacedReceiver", Exported = true)]
|
||||||
|
[IntentFilter(new[] { Intent.ActionMyPackageReplaced })]
|
||||||
|
public class PackageReplacedReceiver : BroadcastReceiver
|
||||||
|
{
|
||||||
|
public override void OnReceive(Context context, Intent intent)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("App updated!");
|
||||||
|
Helpers.PerformUpdateTasks(Resolver.Resolve<ISettings>(), Resolver.Resolve<IAppInfoService>(),
|
||||||
|
Resolver.Resolve<IDatabaseService>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,57 +1,32 @@
|
|||||||
<?xml version='1.0' encoding='UTF-8'?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2.15.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.11.1" android:installLocation="auto" android:versionCode="502" xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.NFC" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<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.INTERNET"/>
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
<uses-permission android:name="android.permission.NFC"/>
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
|
||||||
<uses-permission android:name="android.permission.CAMERA"/>
|
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
|
||||||
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
|
||||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
|
||||||
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY"/>
|
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false"/>
|
<application android:label="bitwarden" android:theme="@style/BitwardenTheme" android:allowBackup="false">
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
|
<provider
|
||||||
|
android:name="android.support.v4.content.FileProvider"
|
||||||
<application android:label="Bitwarden" android:theme="@style/LaunchTheme" android:allowBackup="false" tools:replace="android:allowBackup" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:networkSecurityConfig="@xml/network_security_config">
|
android:authorities="com.x8bit.bitwarden.fileprovider"
|
||||||
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.x8bit.bitwarden.fileprovider" android:exported="false" android:grantUriPermissions="true">
|
android:exported="false"
|
||||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths"/>
|
android:grantUriPermissions="true">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||||
|
android:resource="@xml/filepaths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<meta-data android:name="android.max_aspect" android:value="2.1"/>
|
<activity android:name="net.hockeyapp.android.UpdateActivity" android:exported="false" android:icon="@drawable/icon" />
|
||||||
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions"/>
|
|
||||||
|
|
||||||
<!-- Support for Samsung "Multi Window" mode (for Android < 7.0 users) -->
|
|
||||||
<meta-data android:name="com.samsung.android.sdk.multiwindow.enable" android:value="true"/>
|
|
||||||
<meta-data android:name="com.samsung.android.sdk.multiwindow.penwindow.enable" android:value="true"/>
|
|
||||||
|
|
||||||
<!-- Support for LG "Dual Window" mode (for Android < 7.0 users) -->
|
|
||||||
<meta-data android:name="com.lge.support.SPLIT_WINDOW" android:value="true"/>
|
|
||||||
<!-- Declare MainActivity manually so we can set LaunchMode using API dependant resource -->
|
|
||||||
<activity android:name="com.x8bit.bitwarden.MainActivity" android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|uiMode" android:exported="true" android:icon="@mipmap/ic_launcher" android:label="Bitwarden" android:launchMode="@integer/launchModeAPIlevel" android:theme="@style/LaunchTheme">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
|
||||||
</intent-filter>
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.SEND"/>
|
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
|
||||||
<data android:mimeType="application/*"/>
|
|
||||||
<data android:mimeType="image/*"/>
|
|
||||||
<data android:mimeType="video/*"/>
|
|
||||||
<data android:mimeType="text/*"/>
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
<!-- Package visibility (for Android 11+) -->
|
|
||||||
<queries>
|
|
||||||
<intent>
|
|
||||||
<action android:name="*"/>
|
|
||||||
</intent>
|
|
||||||
</queries>
|
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Android.App;
|
||||||
|
|
||||||
// General Information about an assembly is controlled through the following
|
// General Information about an assembly is controlled through the following
|
||||||
// set of attributes. Change these attribute values to modify the information
|
// set of attributes. Change these attribute values to modify the information
|
||||||
@@ -7,9 +9,9 @@ using System.Runtime.InteropServices;
|
|||||||
[assembly: AssemblyTitle("BitwardenAndroid")]
|
[assembly: AssemblyTitle("BitwardenAndroid")]
|
||||||
[assembly: AssemblyDescription("")]
|
[assembly: AssemblyDescription("")]
|
||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("Bitwarden Inc.")]
|
[assembly: AssemblyCompany("8bit Solutions LLC")]
|
||||||
[assembly: AssemblyProduct("Bitwarden")]
|
[assembly: AssemblyProduct("bitwarden")]
|
||||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
[assembly: ComVisible(false)]
|
[assembly: ComVisible(false)]
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
#if !FDROID
|
|
||||||
using Android.App;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Firebase.Messaging;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Push
|
|
||||||
{
|
|
||||||
[Service(Exported=false)]
|
|
||||||
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
|
|
||||||
public class FirebaseMessagingService : Firebase.Messaging.FirebaseMessagingService
|
|
||||||
{
|
|
||||||
public async override void OnNewToken(string token)
|
|
||||||
{
|
|
||||||
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
|
||||||
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
|
|
||||||
|
|
||||||
await storageService.SaveAsync(Core.Constants.PushRegisteredTokenKey, token);
|
|
||||||
await pushNotificationService.RegisterAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async override void OnMessageReceived(RemoteMessage message)
|
|
||||||
{
|
|
||||||
if (message?.Data == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
|
|
||||||
if (data == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var obj = JObject.Parse(data);
|
|
||||||
var listener = ServiceContainer.Resolve<IPushNotificationListenerService>(
|
|
||||||
"pushNotificationListenerService");
|
|
||||||
await listener.OnMessageAsync(obj, Device.Android);
|
|
||||||
}
|
|
||||||
catch (JsonReaderException ex)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine(ex.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using Android.Content;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Receivers
|
|
||||||
{
|
|
||||||
[BroadcastReceiver(Name = "com.x8bit.bitwarden.ClearClipboardAlarmReceiver", Exported = false)]
|
|
||||||
public class ClearClipboardAlarmReceiver : BroadcastReceiver
|
|
||||||
{
|
|
||||||
public override void OnReceive(Context context, Intent intent)
|
|
||||||
{
|
|
||||||
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
|
|
||||||
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
using Android.Content;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Receivers
|
|
||||||
{
|
|
||||||
[BroadcastReceiver(Name = "com.x8bit.bitwarden.EventUploadReceiver", Exported = false)]
|
|
||||||
public class EventUploadReceiver : BroadcastReceiver
|
|
||||||
{
|
|
||||||
public async override void OnReceive(Context context, Intent intent)
|
|
||||||
{
|
|
||||||
var eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
|
||||||
await eventService.UploadEventsAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
|
||||||
using Bit.App.Abstractions;
|
|
||||||
using Bit.App.Utilities;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Receivers
|
|
||||||
{
|
|
||||||
[BroadcastReceiver(Name = "com.x8bit.bitwarden.PackageReplacedReceiver", Exported = false)]
|
|
||||||
[IntentFilter(new[] { Intent.ActionMyPackageReplaced })]
|
|
||||||
public class PackageReplacedReceiver : BroadcastReceiver
|
|
||||||
{
|
|
||||||
public override async void OnReceive(Context context, Intent intent)
|
|
||||||
{
|
|
||||||
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
|
||||||
await AppHelpers.PerformUpdateTasksAsync(ServiceContainer.Resolve<ISyncService>("syncService"),
|
|
||||||
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"), storageService);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Android.App;
|
|
||||||
using Android.Content;
|
|
||||||
using Bit.App.Utilities;
|
|
||||||
using Bit.Core.Abstractions;
|
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
|
|
||||||
namespace Bit.Droid.Receivers
|
|
||||||
{
|
|
||||||
[BroadcastReceiver(Name = "com.x8bit.bitwarden.RestrictionsChangedReceiver", Exported = false)]
|
|
||||||
[IntentFilter(new[] { Intent.ActionApplicationRestrictionsChanged })]
|
|
||||||
public class RestrictionsChangedReceiver : BroadcastReceiver
|
|
||||||
{
|
|
||||||
public async override void OnReceive(Context context, Intent intent)
|
|
||||||
{
|
|
||||||
if (intent.Action == Intent.ActionApplicationRestrictionsChanged)
|
|
||||||
{
|
|
||||||
await AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Content.Res;
|
|
||||||
using Android.Views.InputMethods;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(Editor), typeof(CustomEditorRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class CustomEditorRenderer : EditorRenderer
|
|
||||||
{
|
|
||||||
public CustomEditorRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
// Workaround for issue described here:
|
|
||||||
// https://github.com/xamarin/Xamarin.Forms/issues/8291#issuecomment-617456651
|
|
||||||
protected override void OnAttachedToWindow()
|
|
||||||
{
|
|
||||||
base.OnAttachedToWindow();
|
|
||||||
EditText.Enabled = false;
|
|
||||||
EditText.Enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
UpdateBorderColor();
|
|
||||||
if (Control != null && e.NewElement != null)
|
|
||||||
{
|
|
||||||
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
|
|
||||||
Control.PaddingBottom + 20);
|
|
||||||
Control.ImeOptions = Control.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
|
|
||||||
(ImeAction)ImeFlags.NoExtractUi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
|
|
||||||
if (e.PropertyName == Entry.TextColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
UpdateBorderColor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateBorderColor()
|
|
||||||
{
|
|
||||||
if (Control != null)
|
|
||||||
{
|
|
||||||
var states = new[]
|
|
||||||
{
|
|
||||||
new[] { Android.Resource.Attribute.StateFocused }, // focused
|
|
||||||
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
|
|
||||||
};
|
|
||||||
var colors = new int[]
|
|
||||||
{
|
|
||||||
ThemeHelpers.PrimaryColor,
|
|
||||||
ThemeHelpers.MutedColor
|
|
||||||
};
|
|
||||||
Control.BackgroundTintList = new ColorStateList(states, colors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Content.Res;
|
|
||||||
using Android.Graphics;
|
|
||||||
using Android.Text;
|
|
||||||
using Android.Views.InputMethods;
|
|
||||||
using Android.Widget;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(Entry), typeof(CustomEntryRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class CustomEntryRenderer : EntryRenderer
|
|
||||||
{
|
|
||||||
public CustomEntryRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
UpdateBorderColor();
|
|
||||||
if (Control != null && e.NewElement != null)
|
|
||||||
{
|
|
||||||
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
|
|
||||||
Control.PaddingBottom + 20);
|
|
||||||
Control.ImeOptions = Control.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
|
|
||||||
(ImeAction)ImeFlags.NoExtractUi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround for bug preventing long-press -> copy/paste on Android 11
|
|
||||||
// See https://issuetracker.google.com/issues/37095917
|
|
||||||
protected override void OnAttachedToWindow()
|
|
||||||
{
|
|
||||||
base.OnAttachedToWindow();
|
|
||||||
Control.Enabled = false;
|
|
||||||
Control.Enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Workaround for failure to disable text prediction on non-password fields
|
|
||||||
// see https://github.com/xamarin/Xamarin.Forms/issues/10857
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
|
|
||||||
// Check if changed property is "IsPassword", otherwise ignore
|
|
||||||
if (e.PropertyName == Entry.IsPasswordProperty.PropertyName)
|
|
||||||
{
|
|
||||||
// Check if field type is text, otherwise ignore (numeric passwords, etc.)
|
|
||||||
EditText.InputType = Element.Keyboard.ToInputType();
|
|
||||||
bool isText = (EditText.InputType & InputTypes.ClassText) == InputTypes.ClassText,
|
|
||||||
isNumber = (EditText.InputType & InputTypes.ClassNumber) == InputTypes.ClassNumber;
|
|
||||||
if (isText || isNumber)
|
|
||||||
{
|
|
||||||
if (Element.IsPassword)
|
|
||||||
{
|
|
||||||
// Element is a password field, set inputType to TextVariationPassword which disables
|
|
||||||
// predictive text by default
|
|
||||||
EditText.InputType = EditText.InputType |
|
|
||||||
(isText ? InputTypes.TextVariationPassword : InputTypes.NumberVariationPassword);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Element is not a password field, set inputType to TextVariationVisiblePassword to
|
|
||||||
// disable predictive text while still displaying the content.
|
|
||||||
EditText.InputType = EditText.InputType |
|
|
||||||
(isText ? InputTypes.TextVariationVisiblePassword : InputTypes.NumberVariationNormal);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The workaround above forces a reset of the style properties, so we need to re-apply the font.
|
|
||||||
// see https://xamarin.github.io/bugzilla-archives/33/33666/bug.html
|
|
||||||
var typeface = Typeface.CreateFromAsset(Context.Assets, "RobotoMono_Regular.ttf");
|
|
||||||
if (Control is TextView label)
|
|
||||||
{
|
|
||||||
label.Typeface = typeface;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (e.PropertyName == Entry.TextColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
UpdateBorderColor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateBorderColor()
|
|
||||||
{
|
|
||||||
if (Control != null)
|
|
||||||
{
|
|
||||||
var states = new[]
|
|
||||||
{
|
|
||||||
new[] { Android.Resource.Attribute.StateFocused }, // focused
|
|
||||||
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
|
|
||||||
};
|
|
||||||
var colors = new int[]
|
|
||||||
{
|
|
||||||
ThemeHelpers.PrimaryColor,
|
|
||||||
ThemeHelpers.MutedColor
|
|
||||||
};
|
|
||||||
Control.BackgroundTintList = new ColorStateList(states, colors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Content.Res;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(Picker), typeof(CustomPickerRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class CustomPickerRenderer : PickerRenderer
|
|
||||||
{
|
|
||||||
public CustomPickerRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
UpdateBorderColor();
|
|
||||||
if (Control != null && e.NewElement != null)
|
|
||||||
{
|
|
||||||
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
|
|
||||||
Control.PaddingBottom + 20);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
|
|
||||||
if (e.PropertyName == Picker.TextColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
UpdateBorderColor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateBorderColor()
|
|
||||||
{
|
|
||||||
if (Control != null)
|
|
||||||
{
|
|
||||||
var states = new[]
|
|
||||||
{
|
|
||||||
new[] { Android.Resource.Attribute.StateFocused }, // focused
|
|
||||||
new[] { -Android.Resource.Attribute.StateFocused }, // unfocused
|
|
||||||
};
|
|
||||||
var colors = new int[]
|
|
||||||
{
|
|
||||||
ThemeHelpers.PrimaryColor,
|
|
||||||
ThemeHelpers.MutedColor
|
|
||||||
};
|
|
||||||
Control.BackgroundTintList = new ColorStateList(states, colors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
using Android.Content;
|
|
||||||
using Android.Views.InputMethods;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class CustomSearchBarRenderer : SearchBarRenderer
|
|
||||||
{
|
|
||||||
public CustomSearchBarRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
if (Control != null && e.NewElement != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var magId = Resources.GetIdentifier("android:id/search_mag_icon", null, null);
|
|
||||||
var magImage = (Android.Widget.ImageView)Control.FindViewById(magId);
|
|
||||||
magImage.LayoutParameters = new Android.Widget.LinearLayout.LayoutParams(0, 0);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
Control.SetImeOptions(Control.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
|
|
||||||
(ImeAction)ImeFlags.NoExtractUi);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Content.Res;
|
|
||||||
using Android.Graphics.Drawables;
|
|
||||||
using Android.OS;
|
|
||||||
using AndroidX.Core.Content.Resources;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Bit.Droid.Utilities;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(Switch), typeof(CustomSwitchRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class CustomSwitchRenderer : SwitchRenderer
|
|
||||||
{
|
|
||||||
public CustomSwitchRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{}
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Switch> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
UpdateColors();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
|
|
||||||
if (e.PropertyName == Switch.OnColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
UpdateColors();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateColors()
|
|
||||||
{
|
|
||||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.LollipopMr1)
|
|
||||||
{
|
|
||||||
// Android 5.x doesn't support ThumbTintList, and using SwitchCompat on every version after 5.x
|
|
||||||
// doesn't apply tinting the way we want. Let 5.x to do its own thing here.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Control != null)
|
|
||||||
{
|
|
||||||
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null);
|
|
||||||
if (t is GradientDrawable thumb)
|
|
||||||
{
|
|
||||||
Control.ThumbDrawable = thumb;
|
|
||||||
}
|
|
||||||
var thumbStates = new[]
|
|
||||||
{
|
|
||||||
new[] { Android.Resource.Attribute.StateChecked }, // checked
|
|
||||||
new[] { -Android.Resource.Attribute.StateChecked }, // unchecked
|
|
||||||
};
|
|
||||||
var thumbColors = new int[]
|
|
||||||
{
|
|
||||||
ThemeHelpers.SwitchOnColor,
|
|
||||||
ThemeHelpers.SwitchThumbColor
|
|
||||||
};
|
|
||||||
Control.ThumbTintList = new ColorStateList(thumbStates, thumbColors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
using Android.Content;
|
|
||||||
using Android.Views;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Google.Android.Material.BottomNavigation;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
using Xamarin.Forms.Platform.Android.AppCompat;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class CustomTabbedRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemReselectedListener
|
|
||||||
{
|
|
||||||
private TabbedPage _page;
|
|
||||||
|
|
||||||
public CustomTabbedRenderer(Context context) : base(context) { }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
if (e.NewElement != null)
|
|
||||||
{
|
|
||||||
_page = e.NewElement;
|
|
||||||
GetBottomNavigationView()?.SetOnNavigationItemReselectedListener(this);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_page = e.OldElement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private BottomNavigationView GetBottomNavigationView()
|
|
||||||
{
|
|
||||||
for (var i = 0; i < ViewGroup.ChildCount; i++)
|
|
||||||
{
|
|
||||||
var childView = ViewGroup.GetChildAt(i);
|
|
||||||
if (childView is ViewGroup viewGroup)
|
|
||||||
{
|
|
||||||
for (var j = 0; j < viewGroup.ChildCount; j++)
|
|
||||||
{
|
|
||||||
var childRelativeLayoutView = viewGroup.GetChildAt(j);
|
|
||||||
if (childRelativeLayoutView is BottomNavigationView bottomNavigationView)
|
|
||||||
{
|
|
||||||
return bottomNavigationView;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnNavigationItemReselected(IMenuItem item)
|
|
||||||
{
|
|
||||||
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
|
|
||||||
{
|
|
||||||
Device.BeginInvokeOnMainThread(async () => await _page.CurrentPage.Navigation.PopToRootAsync());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Views;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedDatePicker), typeof(ExtendedDatePickerRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class ExtendedDatePickerRenderer : DatePickerRenderer
|
|
||||||
{
|
|
||||||
public ExtendedDatePickerRenderer(Context context)
|
|
||||||
: base(context) { }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<DatePicker> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
if (Control != null && Element is ExtendedDatePicker element)
|
|
||||||
{
|
|
||||||
// center text
|
|
||||||
Control.Gravity = GravityFlags.CenterHorizontal;
|
|
||||||
|
|
||||||
// use placeholder until NullableDate set
|
|
||||||
if (!element.NullableDate.HasValue)
|
|
||||||
{
|
|
||||||
Control.Text = element.PlaceHolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.PropertyName == DatePicker.DateProperty.PropertyName ||
|
|
||||||
e.PropertyName == DatePicker.FormatProperty.PropertyName)
|
|
||||||
{
|
|
||||||
if (Control != null && Element is ExtendedDatePicker element)
|
|
||||||
{
|
|
||||||
if (Element.Format == element.PlaceHolder)
|
|
||||||
{
|
|
||||||
Control.Text = element.PlaceHolder;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using Android.Content;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedGrid), typeof(ExtendedGridRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class ExtendedGridRenderer : ViewRenderer
|
|
||||||
{
|
|
||||||
public ExtendedGridRenderer(Context context) : base(context) { }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<View> elementChangedEvent)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(elementChangedEvent);
|
|
||||||
if (elementChangedEvent.NewElement != null)
|
|
||||||
{
|
|
||||||
SetBackgroundResource(Resource.Drawable.list_item_bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Graphics.Drawables;
|
|
||||||
using AndroidX.Core.Content.Resources;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedSlider), typeof(ExtendedSliderRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class ExtendedSliderRenderer : SliderRenderer
|
|
||||||
{
|
|
||||||
public ExtendedSliderRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{}
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
UpdateColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
|
|
||||||
if (e.PropertyName == ExtendedSlider.ThumbBorderColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
UpdateColor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateColor()
|
|
||||||
{
|
|
||||||
if (Control != null && Element is ExtendedSlider view)
|
|
||||||
{
|
|
||||||
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.slider_thumb, null);
|
|
||||||
if (t is GradientDrawable thumb)
|
|
||||||
{
|
|
||||||
if (view.ThumbColor == Color.Default)
|
|
||||||
{
|
|
||||||
thumb.SetColor(Color.White.ToAndroid());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
thumb.SetColor(view.ThumbColor.ToAndroid());
|
|
||||||
}
|
|
||||||
thumb.SetStroke(3, view.ThumbBorderColor.ToAndroid());
|
|
||||||
Control.SetThumb(thumb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
using Android.Content;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedStackLayout), typeof(ExtendedStackLayoutRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class ExtendedStackLayoutRenderer : ViewRenderer
|
|
||||||
{
|
|
||||||
public ExtendedStackLayoutRenderer(Context context) : base(context) { }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<View> elementChangedEvent)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(elementChangedEvent);
|
|
||||||
if (elementChangedEvent.NewElement != null)
|
|
||||||
{
|
|
||||||
SetBackgroundResource(Resource.Drawable.list_item_bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Graphics;
|
|
||||||
using Android.OS;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedStepper), typeof(ExtendedStepperRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class ExtendedStepperRenderer : StepperRenderer
|
|
||||||
{
|
|
||||||
public ExtendedStepperRenderer(Context context)
|
|
||||||
: base(context)
|
|
||||||
{}
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Stepper> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
UpdateBgColor();
|
|
||||||
UpdateFgColor();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
|
|
||||||
if (e.PropertyName == ExtendedStepper.StepperBackgroundColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
UpdateBgColor();
|
|
||||||
}
|
|
||||||
else if (e.PropertyName == ExtendedStepper.StepperForegroundColorProperty.PropertyName)
|
|
||||||
{
|
|
||||||
UpdateFgColor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateBgColor()
|
|
||||||
{
|
|
||||||
if (Control != null && Element is ExtendedStepper view)
|
|
||||||
{
|
|
||||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
|
|
||||||
{
|
|
||||||
Control.GetChildAt(0)?.Background?.SetColorFilter(
|
|
||||||
new BlendModeColorFilter(view.StepperBackgroundColor.ToAndroid(), BlendMode.Multiply));
|
|
||||||
Control.GetChildAt(1)?.Background?.SetColorFilter(
|
|
||||||
new BlendModeColorFilter(view.StepperBackgroundColor.ToAndroid(), BlendMode.Multiply));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Control.GetChildAt(0)?.Background?.SetColorFilter(
|
|
||||||
view.StepperBackgroundColor.ToAndroid(), PorterDuff.Mode.Multiply);
|
|
||||||
Control.GetChildAt(1)?.Background?.SetColorFilter(
|
|
||||||
view.StepperBackgroundColor.ToAndroid(), PorterDuff.Mode.Multiply);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateFgColor()
|
|
||||||
{
|
|
||||||
if (Control != null && Element is ExtendedStepper view)
|
|
||||||
{
|
|
||||||
var btn0 = Control.GetChildAt(0) as Android.Widget.Button;
|
|
||||||
btn0?.SetTextColor(view.StepperForegroundColor.ToAndroid());
|
|
||||||
var btn1 = Control.GetChildAt(1) as Android.Widget.Button;
|
|
||||||
btn1?.SetTextColor(view.StepperForegroundColor.ToAndroid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
using System.ComponentModel;
|
|
||||||
using Android.Content;
|
|
||||||
using Android.Views;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(ExtendedTimePicker), typeof(ExtendedTimePickerRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class ExtendedTimePickerRenderer : TimePickerRenderer
|
|
||||||
{
|
|
||||||
public ExtendedTimePickerRenderer(Context context)
|
|
||||||
: base(context) { }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<TimePicker> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
if (Control != null && Element is ExtendedTimePicker element)
|
|
||||||
{
|
|
||||||
// center text
|
|
||||||
Control.Gravity = GravityFlags.CenterHorizontal;
|
|
||||||
|
|
||||||
// use placeholder until NullableTime set
|
|
||||||
if (!element.NullableTime.HasValue)
|
|
||||||
{
|
|
||||||
Control.Text = element.PlaceHolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
|
||||||
{
|
|
||||||
if (e.PropertyName == TimePicker.TimeProperty.PropertyName ||
|
|
||||||
e.PropertyName == TimePicker.FormatProperty.PropertyName)
|
|
||||||
{
|
|
||||||
if (Control != null && Element is ExtendedTimePicker element)
|
|
||||||
{
|
|
||||||
if (Element.Format == element.PlaceHolder)
|
|
||||||
{
|
|
||||||
Control.Text = element.PlaceHolder;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
base.OnElementPropertyChanged(sender, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using System;
|
|
||||||
using Android.Content;
|
|
||||||
using Bit.App.Controls;
|
|
||||||
using Bit.Droid.Renderers;
|
|
||||||
using Xamarin.Forms;
|
|
||||||
using Xamarin.Forms.Platform.Android;
|
|
||||||
|
|
||||||
[assembly: ExportRenderer(typeof(SelectableLabel), typeof(SelectableLabelRenderer))]
|
|
||||||
namespace Bit.Droid.Renderers
|
|
||||||
{
|
|
||||||
public class SelectableLabelRenderer : LabelRenderer
|
|
||||||
{
|
|
||||||
public SelectableLabelRenderer(Context context) : base(context) { }
|
|
||||||
|
|
||||||
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
|
|
||||||
{
|
|
||||||
base.OnElementChanged(e);
|
|
||||||
|
|
||||||
if (Control != null)
|
|
||||||
{
|
|
||||||
Control.SetTextIsSelectable(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
50
src/Android/Resources/AboutResources.txt
Normal file
50
src/Android/Resources/AboutResources.txt
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
Images, layout descriptions, binary blobs and string dictionaries can be included
|
||||||
|
in your application as resource files. Various Android APIs are designed to
|
||||||
|
operate on the resource IDs instead of dealing with images, strings or binary blobs
|
||||||
|
directly.
|
||||||
|
|
||||||
|
For example, a sample Android app that contains a user interface layout (main.xml),
|
||||||
|
an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
|
||||||
|
would keep its resources in the "Resources" directory of the application:
|
||||||
|
|
||||||
|
Resources/
|
||||||
|
drawable-hdpi/
|
||||||
|
icon.png
|
||||||
|
|
||||||
|
drawable-ldpi/
|
||||||
|
icon.png
|
||||||
|
|
||||||
|
drawable-mdpi/
|
||||||
|
icon.png
|
||||||
|
|
||||||
|
layout/
|
||||||
|
main.xml
|
||||||
|
|
||||||
|
values/
|
||||||
|
strings.xml
|
||||||
|
|
||||||
|
In order to get the build system to recognize Android resources, set the build action to
|
||||||
|
"AndroidResource". The native Android APIs do not operate directly with filenames, but
|
||||||
|
instead operate on resource IDs. When you compile an Android application that uses resources,
|
||||||
|
the build system will package the resources for distribution and generate a class called
|
||||||
|
"Resource" that contains the tokens for each one of the resources included. For example,
|
||||||
|
for the above Resources layout, this is what the Resource class would expose:
|
||||||
|
|
||||||
|
public class Resource {
|
||||||
|
public class drawable {
|
||||||
|
public const int icon = 0x123;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class layout {
|
||||||
|
public const int main = 0x456;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class strings {
|
||||||
|
public const int first_string = 0xabc;
|
||||||
|
public const int second_string = 0xbcd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main
|
||||||
|
to reference the layout/main.xml file, or Resource.strings.first_string to reference the first
|
||||||
|
string in the dictionary file values/strings.xml.
|
||||||
7206
src/Android/Resources/Resource.Designer.cs
generated
Normal file
7206
src/Android/Resources/Resource.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
BIN
src/Android/Resources/drawable-hdpi/accessibility_step1.png
Normal file
BIN
src/Android/Resources/drawable-hdpi/accessibility_step1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
src/Android/Resources/drawable-hdpi/accessibility_step2.png
Normal file
BIN
src/Android/Resources/drawable-hdpi/accessibility_step2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.6 KiB |
BIN
src/Android/Resources/drawable-hdpi/camera.png
Normal file
BIN
src/Android/Resources/drawable-hdpi/camera.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 664 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user