1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Compare commits

..

10 Commits

Author SHA1 Message Date
github-actions[bot]
c50028d0d3 Bumped version to 2022.10.0 (#2130)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
(cherry picked from commit a5ad43b134)
2022-10-12 17:43:40 +01:00
André Bispo
0b9a16beef [SG-690] DateTime to Utc fix (#2115)
* [SG-690] DateTime to Utc fix

* [SG-690] Removed Utc from server side datetime.

(cherry picked from commit 1e5eab0574)
2022-10-05 16:24:04 -04:00
André Bispo
9b93dbb8e3 [SG-687] added try catch to cancellation token disposal. (#2114) 2022-10-04 20:27:51 +01:00
André Bispo
261610b700 [SG-691] Login request is not displayed after changing accounts (#2111) 2022-10-04 11:47:23 +01:00
André Bispo
fd18dccce9 [SG-687] Request time not updating (#2108) 2022-10-04 11:46:34 +01:00
André Bispo
04daaf4e3a [SG-690] Login Request does not disappear after 15 minutes (#2106) 2022-10-04 11:44:49 +01:00
André Bispo
a08d89a002 [SG-696] Android notification icon blank (#2105) 2022-10-04 11:42:56 +01:00
Carlos Gonçalves
63e1185537 [SG-666][SG-667] Email is not prefilled and username isn't generated automatically (#2109)
* SG-666 SG-667 - Email is now prefilled for plus addressed email username type
* Username is auto generated upon navigation

* SG-666 - Fixed PR comments
* Added missing property initialization

(cherry picked from commit a890ee6612)
2022-10-03 12:40:15 -04:00
mp-bw
1e8a6ca81f added a11y disclosure prompt for Android (#2102) 2022-09-28 10:46:11 -04:00
André Bispo
6fe7e9ce1b Passwordless feature branch PR (#2100)
* [SG-471] Passwordless device login screen (#2017)

* [SSG-471] Added UI for the device login request response.

* [SG-471] Added text resources and arguments to Page.

* [SG-471] Added properties to speed up page bindings

* [SG-471] Added mock services. Added Accept/reject command binding, navigation and toast messages.

* [SG-471] fixed code styling with dotnet-format

* [SG-471] Fixed back button placement. PR fixes.

* [SG-471] Added new Origin parameter to the page.

* [SG-471] PR Fixes

* [SG-471] PR fixes

* [SG-471] PR Fix: added FireAndForget.

* [SG-471] Moved fire and forget to run on ui thread task.

* [SG-381] Passwordless - Add setting to Mobile (#2037)

* [SG-381] Added settings option to approve passwordless login request. If user has notifications disabled, prompt to go to settings and enable them.

* [SG-381] Update settings pop up texts.

* [SG-381] Added new method to get notifications state on device settings. Added userId to property saved on device to differentiate value between users.

* [SG-381] Added text for the popup on selection.

* [SG-381] PR Fixes

* [SG-408] Implement passwordless api methods (#2055)

* [SG-408] Update notification model.

* [SG-408] removed duplicated resource

* [SG-408] Added implementation to Api Service of new passwordless methods.

* removed qa endpoints

* [SG-408] Changed auth methods implementation, added method call to viewmodel.

* [SG-408] ran code format

* [SG-408] PR fixes

* [SG-472] Add configuration for new notification type (#2056)

* [SG-472] Added methods to present local notification to the user. Configured new notification type for passwordless logins

* [SG-472] Updated code to new api service changes.

* [SG-472] ran dotnet format

* [SG-472] PR Fixes.

* [SG-472] PR Fixes

* [SG-169] End-to-end testing refactor. (#2073)

* [SG-169] Passwordless demo change requests (#2079)

* [SG-169] End-to-end testing refactor.

* [SG-169] Fixed labels. Changed color of Fingerprint phrase. Waited for app to be in foreground to launch passwordless modal to fix Android issues.

* [SG-169] Anchored buttons to the bottom of the screen.

* [SG-169] Changed device type from enum to string.

* [SG-169] PR fixes

* [SG-169] PR fixes

* [SG-169] Added comment on static variable

(cherry picked from commit f9a32e4abc)
2022-09-26 20:43:34 -04:00
726 changed files with 11857 additions and 66372 deletions

View File

@@ -7,12 +7,6 @@
"commands": [ "commands": [
"dotnet-format" "dotnet-format"
] ]
},
"cake.tool": {
"version": "2.2.0",
"commands": [
"dotnet-cake"
]
} }
} }
} }

View File

@@ -1,8 +1,5 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Customer Support
url: https://bitwarden.com/contact/
about: Please contact our customer support for account issues and general customer support.
- name: Report mobile autofill failure - name: Report mobile autofill failure
url: https://docs.google.com/forms/d/e/1FAIpQLScMopHyN7KGJs8hW562VTzbIGL4KcFnx0wJcsW0GYE1BnPiGA/viewform 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! 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!
@@ -12,6 +9,9 @@ contact_links:
- name: Bitwarden Community Forums - name: Bitwarden Community Forums
url: https://community.bitwarden.com url: https://community.bitwarden.com
about: Please visit the community forums for general community discussion, support and the development roadmap. 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 - name: Security Issues
url: https://hackerone.com/bitwarden url: https://hackerone.com/bitwarden
about: We use HackerOne to manage security disclosures. about: We use HackerOne to manage security disclosures.

19
.github/labeler.yml vendored
View File

@@ -1,19 +0,0 @@
android:
- src/App/*
- src/Core/*
- src/Android/*
iOS:
- src/App/*
- src/Core/*
- lib/ios/*
- src/iOS/*
- 'src/iOS.Autofill/*'
- 'src/iOS.Core/*'
- 'src/iOS.Extension/*'
- 'src/iOS.ShareExtension/*'
- 'src/iOS.Widget/*'
- src/watchOS/*
watchOS:
- src/watchOS/*

37
.github/renovate.json vendored
View File

@@ -1,37 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
":combinePatchMinorReleases",
":dependencyDashboard",
":maintainLockFilesWeekly",
":pinAllExceptPeerDependencies",
":prConcurrentLimit10",
":rebaseStalePrs",
"schedule:weekends",
":separateMajorReleases"
],
"enabledManagers": ["cargo", "github-actions", "npm", "nuget"],
"packageRules": [
{
"groupName": "cargo minor",
"matchManagers": ["cargo"],
"matchUpdateTypes": ["minor", "patch"]
},
{
"groupName": "gh minor",
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"]
},
{
"groupName": "npm minor",
"matchManagers": ["npm"],
"matchUpdateTypes": ["minor", "patch"]
},
{
"groupName": "nuget minor",
"matchManagers": ["nuget"],
"matchUpdateTypes": ["minor", "patch"]
},
]
}

View File

@@ -7,17 +7,13 @@
<key>provisioningProfiles</key> <key>provisioningProfiles</key>
<dict> <dict>
<key>com.8bit.bitwarden</key> <key>com.8bit.bitwarden</key>
<string>Dist: Bitwarden</string> <string>Dist: Bitwarden 2021</string>
<key>com.8bit.bitwarden.autofill</key> <key>com.8bit.bitwarden.autofill</key>
<string>Dist: Autofill</string> <string>Dist: Autofill 2021</string>
<key>com.8bit.bitwarden.find-login-action-extension</key> <key>com.8bit.bitwarden.find-login-action-extension</key>
<string>Dist: Extension</string> <string>Dist: Extension 2021</string>
<key>com.8bit.bitwarden.share-extension</key> <key>com.8bit.bitwarden.share-extension</key>
<string>Dist: Share Extension</string> <string>Dist: Share Extension 2021</string>
<key>com.8bit.bitwarden.watchkitapp</key>
<string>Dist: Bitwarden Watch App</string>
<key>com.8bit.bitwarden.watchkitapp.watchkitextension</key>
<string>Dist: Bitwarden Watch App Extension</string>
</dict> </dict>
</dict> </dict>
</plist> </plist>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -14,7 +14,7 @@ jobs:
# Feature request # Feature request
- if: github.event.label.name == 'feature-request' - if: github.event.label.name == 'feature-request'
name: Feature request name: Feature request
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1 uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with: with:
comment: | comment: |
We use GitHub issues as a place to track bugs and other development related issues. The [Bitwarden Community Forums](https://community.bitwarden.com/) has a [Feature Requests](https://community.bitwarden.com/c/feature-requests) section for submitting, voting for, and discussing requests like this one. We use GitHub issues as a place to track bugs and other development related issues. The [Bitwarden Community Forums](https://community.bitwarden.com/) has a [Feature Requests](https://community.bitwarden.com/c/feature-requests) section for submitting, voting for, and discussing requests like this one.
@@ -25,7 +25,7 @@ jobs:
# Intended behavior # Intended behavior
- if: github.event.label.name == 'intended-behavior' - if: github.event.label.name == 'intended-behavior'
name: Intended behaviour name: Intended behaviour
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1 uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with: with:
comment: | comment: |
Your issue appears to be describing the intended behavior of the software. If you want this to be changed, it would be a feature request. Your issue appears to be describing the intended behavior of the software. If you want this to be changed, it would be a feature request.
@@ -38,7 +38,7 @@ jobs:
# Customer support request # Customer support request
- if: github.event.label.name == 'customer-support' - if: github.event.label.name == 'customer-support'
name: Customer Support request name: Customer Support request
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1 uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with: with:
comment: | comment: |
We use GitHub issues as a place to track bugs and other development related issues. Your issue appears to be a support request, or would otherwise be better handled by our dedicated Customer Success team. We use GitHub issues as a place to track bugs and other development related issues. Your issue appears to be a support request, or would otherwise be better handled by our dedicated Customer Success team.
@@ -49,14 +49,14 @@ jobs:
# Resolved # Resolved
- if: github.event.label.name == 'resolved' - if: github.event.label.name == 'resolved'
name: Resolved name: Resolved
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1 uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with: with:
comment: | comment: |
Weve closed this issue, as it appears the original problem has been resolved. If this happens again or continues to be an problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis. Weve closed this issue, as it appears the original problem has been resolved. If this happens again or continues to be an problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis.
# Stale # Stale
- if: github.event.label.name == 'stale' - if: github.event.label.name == 'stale'
name: Stale name: Stale
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1 uses: peter-evans/close-issue@849549ba7c3a595a064c4b2c56f206ee78f93515 # v2.0.0
with: with:
comment: | comment: |
As we havent heard from you about this problem in some time, this issue will now be closed. As we havent heard from you about this problem in some time, this issue will now be closed.

View File

@@ -4,10 +4,10 @@ name: Build
on: on:
push: push:
branches-ignore: branches-ignore:
- "l10n_master" - 'l10n_master'
- "gh-pages" - 'gh-pages'
paths-ignore: paths-ignore:
- ".github/workflows/**" - '.github/workflows/**'
workflow_dispatch: workflow_dispatch:
inputs: {} inputs: {}
@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Set up CLOC - name: Set up CLOC
run: | run: |
@@ -36,23 +36,21 @@ jobs:
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }} hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
with:
submodules: 'true'
- name: Check if special branches exist - name: Check if special branches exist
id: branch-check id: branch-check
run: | run: |
if [[ $(git ls-remote --heads origin rc) ]]; then if [[ $(git ls-remote --heads origin rc) ]]; then
echo "rc_branch_exists=1" >> $GITHUB_OUTPUT echo "::set-output name=rc_branch_exists::1"
else else
echo "rc_branch_exists=0" >> $GITHUB_OUTPUT echo "::set-output name=rc_branch_exists::0"
fi fi
if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then
echo "hotfix_branch_exists=1" >> $GITHUB_OUTPUT echo "::set-output name=hotfix_branch_exists::1"
else else
echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT echo "::set-output name=hotfix_branch_exists::0"
fi fi
shell: bash shell: bash
@@ -61,26 +59,14 @@ jobs:
name: Android name: Android
runs-on: windows-2022 runs-on: windows-2022
needs: setup needs: setup
strategy:
fail-fast: false
matrix:
variant: ["prod", "qa"]
steps: steps:
- name: Setup NuGet - name: Setup NuGet
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0 uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with: with:
nuget-version: 5.9.0 nuget-version: 5.9.0
- name: Set up .NET
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
with:
dotnet-version: '3.1.x'
- name: Set up MSBuild - name: Set up MSBuild
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1 uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
- name: Setup Windows builder
run: choco install checksum --no-progress
- name: Work Around for broken Windows 2022 Runner Image - name: Work Around for broken Windows 2022 Runner Image
run: | run: |
@@ -101,6 +87,7 @@ jobs:
Write-Host "components were not installed" Write-Host "components were not installed"
exit 1 exit 1
} }
- name: Print environment - name: Print environment
run: | run: |
nuget help | grep Version nuget help | grep Version
@@ -110,9 +97,8 @@ jobs:
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
with:
fetch-depth: 0
- name: Decrypt secrets - name: Decrypt secrets
env: env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }} DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
@@ -123,17 +109,12 @@ jobs:
--output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg --output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg --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" \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg --output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
shell: bash shell: bash
- name: Decrypt secrets - Google Services
if: ${{ matrix.variant == 'prod' }}
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
run: |
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
shell: bash
- name: Increment version - name: Increment version
run: | run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER)) BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
@@ -158,48 +139,29 @@ jobs:
shell: pwsh shell: pwsh
- name: Run Core tests - name: Run Core tests
run: dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx" run: dotnet test test/Core.Test/Core.Test.csproj
shell: pwsh
- name: Report test results
uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226 # v1.6.0
if: always()
with:
name: Test Results
path: "**/test-results.trx"
reporter: dotnet-trx
fail-on-error: true
- name: Build Play Store publisher - name: Build Play Store publisher
if: ${{ matrix.variant == 'prod' }}
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
- name: Setup Android build (${{ matrix.variant }}) - name: Build for Play Store
run: dotnet cake build.cake --target Android --variant ${{ matrix.variant }}
- name: Build Android
run: | run: |
$configuration = "Release"; $configuration = "Release";
Write-Output "########################################" Write-Output "########################################"
Write-Output "##### Build $configuration Configuration" Write-Output "##### Build $configuration Configuration"
Write-Output "########################################" Write-Output "########################################"
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
shell: pwsh shell: pwsh
- name: Sign Android Build - name: Sign for Play Store
env: env:
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }} PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }} UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
run: | run: |
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj"); $androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
$packageName = "com.x8bit.bitwarden";
if ("${{ matrix.variant }}" -ne "prod")
{
$packageName = "com.x8bit.bitwarden.${{ matrix.variant }}";
}
Write-Output "########################################" Write-Output "########################################"
Write-Output "##### Sign Google Play Bundle Release Configuration" Write-Output "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################" Write-Output "########################################"
@@ -213,8 +175,9 @@ jobs:
Write-Output "##### Copy Google Play Bundle to project root" Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################" Write-Output "########################################"
$signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.aab"); $signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab");
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).aab"); $signedAabDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.aab");
Copy-Item $signedAabPath $signedAabDestPath Copy-Item $signedAabPath $signedAabDestPath
Write-Output "########################################" Write-Output "########################################"
@@ -230,69 +193,33 @@ jobs:
Write-Output "##### Copy Release APK to project root" Write-Output "##### Copy Release APK to project root"
Write-Output "########################################" Write-Output "########################################"
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.apk"); $signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).apk"); $signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.apk");
Copy-Item $signedApkPath $signedApkDestPath Copy-Item $signedApkPath $signedApkDestPath
shell: pwsh shell: pwsh
- name: Upload Prod .aab artifact
if: ${{ matrix.variant == 'prod' }} - name: Upload Play Store .aab artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0 uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with: with:
name: com.x8bit.bitwarden.aab name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab path: ./com.x8bit.bitwarden.aab
if-no-files-found: error if-no-files-found: error
- name: Upload Prod .apk artifact - name: Upload Play Store .apk artifact
if: ${{ matrix.variant == 'prod' }} uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with: with:
name: com.x8bit.bitwarden.apk name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk path: ./com.x8bit.bitwarden.apk
if-no-files-found: error if-no-files-found: error
- name: Upload Other .apk artifact
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
if-no-files-found: error
- name: Create checksum for Prod .apk artifact
if: ${{ matrix.variant == 'prod' }}
run: |
checksum -f="./com.x8bit.bitwarden.apk" `
-t sha256 | Out-File -Encoding ASCII ./bw-android-apk-sha256.txt
- name: Create checksum for Other .apk artifact
if: ${{ matrix.variant != 'prod' }}
run: |
checksum -f="./com.x8bit.bitwarden.${{ matrix.variant }}.apk" `
-t sha256 | Out-File -Encoding ASCII ./bw-android-${{ matrix.variant }}-apk-sha256.txt
- name: Upload .apk sha file for prod
if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: bw-android-apk-sha256.txt
path: ./bw-android-apk-sha256.txt
if-no-files-found: error
- name: Upload .apk sha file for other
if: ${{ matrix.variant != 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: bw-android-${{ matrix.variant }}-apk-sha256.txt
path: ./bw-android-${{ matrix.variant }}-apk-sha256.txt
if-no-files-found: error
- name: Deploy to Play Store - name: Deploy to Play Store
if: ${{ matrix.variant == 'prod' && (( github.ref == 'refs/heads/master' if: |
&& needs.setup.outputs.rc_branch_exists == 0 (github.ref == 'refs/heads/master'
&& needs.setup.outputs.hotfix_branch_exists == 0) && needs.setup.outputs.rc_branch_exists == 0
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0) && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc' ) }} || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc'
run: | run: |
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll" PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll"
CREDS_PATH="$HOME/secrets/play_creds.json" CREDS_PATH="$HOME/secrets/play_creds.json"
@@ -308,15 +235,12 @@ jobs:
runs-on: windows-2022 runs-on: windows-2022
steps: steps:
- name: Setup NuGet - name: Setup NuGet
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0 uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with: with:
nuget-version: 5.9.0 nuget-version: 5.9.0
- name: Set up MSBuild - name: Set up MSBuild
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1 uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
- name: Setup Windows builder
run: choco install checksum --no-progress
- name: Work Around for broken Windows 2022 Runner Image - name: Work Around for broken Windows 2022 Runner Image
run: | run: |
@@ -347,7 +271,7 @@ jobs:
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Decrypt secrets - name: Decrypt secrets
env: env:
@@ -482,32 +406,20 @@ jobs:
shell: pwsh shell: pwsh
- name: Upload F-Droid .apk artifact - name: Upload F-Droid .apk artifact
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0 uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with: with:
name: com.x8bit.bitwarden-fdroid.apk name: com.x8bit.bitwarden-fdroid.apk
path: ./com.x8bit.bitwarden-fdroid.apk path: ./com.x8bit.bitwarden-fdroid.apk
if-no-files-found: error if-no-files-found: error
- name: Create checksum for F-Droid artifact
run: |
checksum -f="./com.x8bit.bitwarden-fdroid.apk" `
-t sha256 | Out-File -Encoding ASCII ./bw-fdroid-apk-sha256.txt
- name: Upload F-Droid sha file
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0
with:
name: bw-fdroid-apk-sha256.txt
path: ./bw-fdroid-apk-sha256.txt
if-no-files-found: error
ios: ios:
name: Apple iOS name: Apple iOS
runs-on: macos-12 runs-on: macos-11
needs: setup needs: setup
steps: steps:
- name: Setup NuGet - name: Setup NuGet
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0 uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
with: with:
nuget-version: 5.9.0 nuget-version: 5.9.0
@@ -520,19 +432,17 @@ jobs:
echo "GitHub event: $GITHUB_EVENT" echo "GitHub event: $GITHUB_EVENT"
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
with:
submodules: 'true'
- name: Login to Azure - CI Subscription - name: Login to Azure - Prod Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
env: env:
KEYVAULT: bitwarden-ci KEYVAULT: bitwarden-prod-kv
SECRETS: | SECRETS: |
appcenter-ios-token appcenter-ios-token
run: | run: |
@@ -540,7 +450,7 @@ jobs:
do do
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv) VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
echo "::add-mask::$VALUE" echo "::add-mask::$VALUE"
echo "$i=$VALUE" >> $GITHUB_OUTPUT echo "::set-output name=$i::$VALUE"
done done
- name: Decrypt secrets - name: Decrypt secrets
@@ -562,14 +472,6 @@ jobs:
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \ gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_share_extension.mobileprovision \ --output $HOME/secrets/dist_share_extension.mobileprovision \
./.github/secrets/dist_share_extension.mobileprovision.gpg ./.github/secrets/dist_share_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_watch_app.mobileprovision \
./.github/secrets/dist_watch_app.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_watch_app_extension.mobileprovision \
./.github/secrets/dist_watch_app_extension.mobileprovision.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/watchOS/bitwarden/GoogleService-Info.plist ./.github/secrets/GoogleService-Info.plist.gpg
shell: bash shell: bash
- name: Increment version - name: Increment version
@@ -584,9 +486,6 @@ jobs:
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.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 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
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.ShareExtension/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.ShareExtension/Info.plist
cd src/watchOS/bitwarden
agvtool new-version -all $BUILD_NUMBER
cd ../../..
shell: bash shell: bash
- name: Update Entitlements - name: Update Entitlements
@@ -621,8 +520,6 @@ jobs:
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision
SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_share_extension.mobileprovision SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_share_extension.mobileprovision
WATCH_APP_PROFILE_PATH=$HOME/secrets/dist_watch_app.mobileprovision
WATCH_APP_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_watch_app_extension.mobileprovision
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
mkdir -p "$PROFILES_DIR_PATH" mkdir -p "$PROFILES_DIR_PATH"
@@ -638,25 +535,6 @@ jobs:
SHARE_EXTENSION_UUID=$(grep UUID -A1 -a $SHARE_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}") SHARE_EXTENSION_UUID=$(grep UUID -A1 -a $SHARE_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $SHARE_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$SHARE_EXTENSION_UUID.mobileprovision" cp $SHARE_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$SHARE_EXTENSION_UUID.mobileprovision"
WATCH_APP_UUID=$(grep UUID -A1 -a $WATCH_APP_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $WATCH_APP_PROFILE_PATH "$PROFILES_DIR_PATH/$WATCH_APP_UUID.mobileprovision"
WATCH_APP_EXTENSION_UUID=$(grep UUID -A1 -a $WATCH_APP_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
cp $WATCH_APP_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$WATCH_APP_EXTENSION_UUID.mobileprovision"
shell: bash
- name: Bulid WatchApp
run: |
echo "########################################"
echo "##### Build WatchApp with Release Configuration"
echo "########################################"
xcodebuild archive -workspace ./src/watchOS/bitwarden/bitwarden.xcodeproj/project.xcworkspace -configuration Release -scheme bitwarden\ WatchKit\ App -archivePath ./src/watchOS/bitwarden
echo "########################################"
echo "##### Done"
echo "########################################"
shell: bash shell: bash
- name: Restore packages - name: Restore packages
@@ -694,16 +572,11 @@ jobs:
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs" ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs"
EXPORT_PATH="./bitwarden-export" EXPORT_PATH="./bitwarden-export"
WATCH_ARCHIVE_DSYMS_PATH="./src/watchOS/bitwarden.xcarchive/dSYMs/" cp -r $ARCHIVE_DSYMS_PATH $EXPORT_PATH
WATCH_DSYMS_EXPORT_PATH="$EXPORT_PATH/Watch_dSYMs"
cp -r -v $ARCHIVE_DSYMS_PATH $EXPORT_PATH
mkdir $WATCH_DSYMS_EXPORT_PATH
cp -r -v $WATCH_ARCHIVE_DSYMS_PATH $WATCH_DSYMS_EXPORT_PATH
shell: bash shell: bash
- name: Upload App Store .ipa & dSYMs artifacts - name: Upload App Store .ipa & dSYMs artifacts
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # v3.0.0 uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with: with:
name: Bitwarden iOS name: Bitwarden iOS
path: | path: |
@@ -722,39 +595,23 @@ jobs:
- name: Upload dSYMs to App Center - name: Upload dSYMs to App Center
if: | if: |
(github.ref == 'refs/heads/master' (github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0 && needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_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/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc' || github.ref == 'refs/heads/hotfix-rc'
env: env:
APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }} APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }}
run: appcenter crashes upload-symbols -a bitwarden/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN run: appcenter crashes upload-symbols -a bitwarden/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
shell: bash shell: bash
- name: Upload Watch dSYMs to Firebase Crashlytics
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-rc'
run: |
echo "########################################"
echo "##### Uploading Watch dSYMs to Firebase"
echo "########################################"
find "$HOME/Library/Developer/XCode/DerivedData" -name "upload-symbols" -exec chmod +x {} \; -exec {} -gsp "./src/watchOS/bitwarden/GoogleService-Info.plist" -p ios "./bitwarden-export/Watch_dSYMs" \;
shell: bash
- name: Deploy to App Store - name: Deploy to App Store
if: | if: |
(github.ref == 'refs/heads/master' (github.ref == 'refs/heads/master'
&& needs.setup.outputs.rc_branch_exists == 0 && needs.setup.outputs.rc_branch_exists == 0
&& needs.setup.outputs.hotfix_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/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|| github.ref == 'refs/heads/hotfix-rc' || github.ref == 'refs/heads/hotfix-rc'
env: env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }} APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
@@ -776,17 +633,17 @@ jobs:
_CROWDIN_PROJECT_ID: "269690" _CROWDIN_PROJECT_ID: "269690"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
- name: Login to Azure - CI Subscription - name: Login to Azure
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
env: env:
KEYVAULT: bitwarden-ci KEYVAULT: bitwarden-prod-kv
SECRETS: | SECRETS: |
crowdin-api-token crowdin-api-token
run: | run: |
@@ -794,11 +651,11 @@ jobs:
do do
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv) VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
echo "::add-mask::$VALUE" echo "::add-mask::$VALUE"
echo "$i=$VALUE" >> $GITHUB_OUTPUT echo "::set-output name=$i::$VALUE"
done done
- name: Upload Sources - name: Upload Sources
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0 uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
@@ -822,9 +679,9 @@ jobs:
steps: steps:
- name: Check if any job failed - name: Check if any job failed
if: | if: |
(github.ref == 'refs/heads/master') (github.ref == 'refs/heads/master')
|| (github.ref == 'refs/heads/rc') || (github.ref == 'refs/heads/rc')
|| (github.ref == 'refs/heads/hotfix-rc') || (github.ref == 'refs/heads/hotfix-rc')
env: env:
CLOC_STATUS: ${{ needs.cloc.result }} CLOC_STATUS: ${{ needs.cloc.result }}
ANDROID_STATUS: ${{ needs.android.result }} ANDROID_STATUS: ${{ needs.android.result }}
@@ -844,17 +701,17 @@ jobs:
exit 1 exit 1
fi fi
- name: Login to Azure - CI Subscription - name: Login to Azure - Prod Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
if: failure() if: failure()
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
if: failure() if: failure()
env: env:
KEYVAULT: bitwarden-ci KEYVAULT: bitwarden-prod-kv
SECRETS: | SECRETS: |
devops-alerts-slack-webhook-url devops-alerts-slack-webhook-url
run: | run: |
@@ -862,11 +719,11 @@ jobs:
do do
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv) VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
echo "::add-mask::$VALUE" echo "::add-mask::$VALUE"
echo "$i=$VALUE" >> $GITHUB_OUTPUT echo "::set-output name=$i::$VALUE"
done done
- name: Notify Slack on failure - name: Notify Slack on failure
uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f # v2.0.0 uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33
if: failure() if: failure()
env: env:
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }} SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}

View File

@@ -15,22 +15,29 @@ jobs:
_CROWDIN_PROJECT_ID: "269690" _CROWDIN_PROJECT_ID: "269690"
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
- name: Login to Azure - CI Subscription - name: Login to Azure
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3 uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
with: with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets - name: Retrieve secrets
id: retrieve-secrets id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@34ecb67b2a357795dc893549df0795e7383ff50f env:
with: KEYVAULT: bitwarden-prod-kv
keyvault: "bitwarden-ci" SECRETS: |
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase" crowdin-api-token
run: |
for i in ${SECRETS//,/ }
do
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $i --query value --output tsv)
echo "::add-mask::$VALUE"
echo "::set-output name=$i::$VALUE"
done
- name: Download translations - name: Download translations
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0 uses: crowdin/github-action@12143a68c213f3c6d9913c9e5023224f7231face
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }} CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
@@ -40,12 +47,10 @@ jobs:
upload_sources: false upload_sources: false
upload_translations: false upload_translations: false
download_translations: true download_translations: true
github_user_name: "bitwarden-devops-bot" github_user_name: "github-actions"
github_user_email: "106330231+bitwarden-devops-bot@users.noreply.github.com" github_user_email: "<>"
commit_message: "Autosync the updated translations" commit_message: "Autosync the updated translations"
localization_branch_name: crowdin-auto-sync localization_branch_name: crowdin-auto-sync
create_pull_request: true create_pull_request: true
pull_request_title: "Autosync Crowdin Translations" pull_request_title: "Autosync Crowdin Translations"
pull_request_body: "Autosync the updated translations" pull_request_body: "Autosync the updated translations"
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}

View File

@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Enforce Label - name: Enforce Label
uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # 2.2.2 uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
with: with:
BANNED_LABELS: "hold,needs-qa" BANNED_LABELS: "hold"
BANNED_LABELS_DESCRIPTION: "PRs with the hold or needs-qa labels cannot be merged" BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged"

View File

@@ -1,17 +0,0 @@
---
name: "Pull Request Labeler"
on:
pull_request_target: {}
jobs:
labeler:
name: "Pull Request Labeler"
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-20.04
steps:
- uses: actions/labeler@ba790c862c380240c6d5e7427be5ace9a05c754b # v4.0.3
with:
sync-labels: true

View File

@@ -1,6 +1,5 @@
--- ---
name: Release name: Release
run-name: Release ${{ inputs.release_type }}
on: on:
workflow_dispatch: workflow_dispatch:
@@ -38,11 +37,11 @@ jobs:
fi fi
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Check Release Version - name: Check Release Version
id: version id: version
uses: bitwarden/gh-actions/release-version-check@34ecb67b2a357795dc893549df0795e7383ff50f uses: bitwarden/gh-actions/release-version-check@8f055ef543c7433c967a1b9b04a0f230923233bb
with: with:
release-type: ${{ github.event.inputs.release_type }} release-type: ${{ github.event.inputs.release_type }}
project-type: xamarin project-type: xamarin
@@ -52,11 +51,10 @@ jobs:
id: branch id: branch
run: | run: |
BRANCH_NAME=$(basename ${{ github.ref }}) BRANCH_NAME=$(basename ${{ github.ref }})
echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT echo "::set-output name=branch-name::$BRANCH_NAME"
- name: Create GitHub deployment - name: Create GitHub deployment
if: ${{ github.event.inputs.release_type != 'Dry Run' }} uses: chrnorm/deployment-action@1b599fe41a0ef1f95191e7f2eec4743f2d7dfc48
uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5
id: deployment id: deployment
with: with:
token: '${{ secrets.GITHUB_TOKEN }}' token: '${{ secrets.GITHUB_TOKEN }}'
@@ -64,19 +62,19 @@ jobs:
environment: 'production' environment: 'production'
description: 'Deployment ${{ steps.version.outputs.version }} from branch ${{ steps.branch.outputs.branch-name }}' description: 'Deployment ${{ steps.version.outputs.version }} from branch ${{ steps.branch.outputs.branch-name }}'
task: release task: release
- name: Download all artifacts - name: Download all artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0 uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }} branch: ${{ steps.branch.outputs.branch-name }}
- name: Dry Run - Download all artifacts - name: Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0 uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
@@ -92,9 +90,7 @@ jobs:
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab, artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk, ./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk, ./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
./Bitwarden iOS.zip, ./Bitwarden iOS.zip"
./bw-android-apk-sha256.txt/bw-android-apk-sha256.txt,
./bw-fdroid-apk-sha256.txt/bw-fdroid-apk-sha256.txt"
commit: ${{ github.sha }} commit: ${{ github.sha }}
tag: v${{ steps.version.outputs.version }} tag: v${{ steps.version.outputs.version }}
name: Version ${{ steps.version.outputs.version }} name: Version ${{ steps.version.outputs.version }}
@@ -103,16 +99,16 @@ jobs:
draft: true draft: true
- name: Update deployment status to Success - name: Update deployment status to Success
if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }} if: ${{ success() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1 uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
with: with:
token: '${{ secrets.GITHUB_TOKEN }}' token: '${{ secrets.GITHUB_TOKEN }}'
state: 'success' state: 'success'
deployment-id: ${{ steps.deployment.outputs.deployment_id }} deployment-id: ${{ steps.deployment.outputs.deployment_id }}
- name: Update deployment status to Failure - name: Update deployment status to Failure
if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }} if: ${{ failure() }}
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1 uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
with: with:
token: '${{ secrets.GITHUB_TOKEN }}' token: '${{ secrets.GITHUB_TOKEN }}'
state: 'failure' state: 'failure'
@@ -126,20 +122,20 @@ jobs:
if: inputs.fdroid_publish if: inputs.fdroid_publish
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
- name: Download F-Droid .apk artifact - name: Download F-Droid .apk artifact
if: ${{ github.event.inputs.release_type != 'Dry Run' }} if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0 uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success
branch: ${{ needs.release.outputs.branch-name }} branch: ${{ needs.release.outputs.branch-name }}
name: com.x8bit.bitwarden-fdroid.apk name: com.x8bit.bitwarden-fdroid.apk
- name: Dry Run - Download F-Droid .apk artifact - name: Download F-Droid .apk artifact
if: ${{ github.event.inputs.release_type == 'Dry Run' }} if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0 uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with: with:
workflow: build.yml workflow: build.yml
workflow_conclusion: success workflow_conclusion: success

View File

@@ -2,40 +2,66 @@
name: Version Auto Bump name: Version Auto Bump
on: on:
push: release:
tags: types: [published]
- v**
jobs: jobs:
setup: setup:
name: "Setup" name: "Setup"
runs-on: ubuntu-22.04 runs-on: ubuntu-20.04
outputs: outputs:
version_number: ${{ steps.version.outputs.new-version }} version_number: ${{ steps.version.outputs.new-version }}
steps: steps:
- name: Checkout Branch - name: Checkout Branch
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Calculate bumped version - name: Get version to bump
id: version id: version
env: env:
RELEASE_TAG: ${{ github.ref }} RELEASE_TAG: ${{ github.event.release.tag }}
run: | run: |
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/') CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]\.)([0-9])/\1/')
CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/') CURR_VER=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]\.)([0-9])/\2/')
echo "Current Major: $CURR_MAJOR" echo $CURR_VER
echo "Current Patch: $CURR_PATCH"
NEW_PATCH=$((CURR_PATCH+1)) ((CURR_VER++))
NEW_VER=$CURR_MAJOR.$NEW_PATCH NEW_VER=$CURR_MAJOR$CURR_VER
echo "New Version: $NEW_VER"
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT echo $NEW_VER
echo "::set-output name=new-version::$NEW_VER"
trigger_version_bump: trigger_version_bump:
name: Bump version to ${{ needs.setup.outputs.version_number }} name: "Trigger version bump workflow"
needs: setup runs-on: ubuntu-20.04
uses: ./.github/workflows/version-bump.yml needs:
secrets: - setup
AZURE_PROD_KV_CREDENTIALS: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }} steps:
with: - name: Login to Azure
version_number: ${{ needs.setup.outputs.version_number }} uses: Azure/login@ec3c14589bd3e9312b3cc8c41e6860e258df9010
with:
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
- name: Retrieve secrets
id: retrieve-secrets
env:
KEYVAULT: bitwarden-prod-kv
SECRET: "github-pat-bitwarden-devops-bot-repo-scope"
run: |
VALUE=$(az keyvault secret show --vault-name $KEYVAULT --name $SECRET --query value --output tsv)
echo "::add-mask::$VALUE"
echo "::set-output name=$SECRET::$VALUE"
- name: Call GitHub API to trigger workflow bump
env:
TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
VERSION: ${{ needs.setup.outputs.version_number}}
run: |
JSON_STRING=$(printf '{"ref":"master", "inputs": { "version_number":"%s"}}' "$VERSION")
curl \
-X POST \
-i -u bitwarden-devops-bot:$TOKEN \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/bitwarden/mobile/actions/workflows/version-bump.yml/dispatches \
-d $JSON_STRING

View File

@@ -7,14 +7,6 @@ on:
version_number: version_number:
description: "New Version" description: "New Version"
required: true required: true
workflow_call:
inputs:
version_number:
required: true
type: string
secrets:
AZURE_PROD_KV_CREDENTIALS:
required: true
jobs: jobs:
bump_version: bump_version:
@@ -22,79 +14,61 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Checkout Branch - name: Checkout Branch
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- name: Login to Azure - CI Subscription
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf # v1.4.3
with:
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@34ecb67b2a357795dc893549df0795e7383ff50f
with:
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Import GPG key
uses: crazy-max/ghaction-import-gpg@111c56156bcc6918c056dbef52164cfa583dc549 # v5.2.0
with:
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
git_user_signingkey: true
git_commit_gpgsign: true
- name: Create Version Branch - name: Create Version Branch
run: git switch -c version_bump_${{ github.event.inputs.version_number }} run: |
git switch -c version_bump_${{ github.event.inputs.version_number }}
- name: Bump Version - Android XML - name: Bump Version - Android XML
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with: with:
version: ${{ github.event.inputs.version_number }} version: ${{ github.event.inputs.version_number }}
file_path: "./src/Android/Properties/AndroidManifest.xml" file_path: "./src/Android/Properties/AndroidManifest.xml"
- name: Bump Version - iOS.Autofill - name: Bump Version - iOS.Autofill
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with: with:
version: ${{ github.event.inputs.version_number }} version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS.Autofill/Info.plist" file_path: "./src/iOS.Autofill/Info.plist"
- name: Bump Version - iOS.Extension - name: Bump Version - iOS.Extension
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with: with:
version: ${{ github.event.inputs.version_number }} version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS.Extension/Info.plist" file_path: "./src/iOS.Extension/Info.plist"
- name: Bump Version - iOS.ShareExtension - name: Bump Version - iOS.ShareExtension
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with: with:
version: ${{ github.event.inputs.version_number }} version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS.ShareExtension/Info.plist" file_path: "./src/iOS.ShareExtension/Info.plist"
- name: Bump Version - iOS - name: Bump Version - iOS
uses: bitwarden/gh-actions/version-bump@34ecb67b2a357795dc893549df0795e7383ff50f uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
with: with:
version: ${{ github.event.inputs.version_number }} version: ${{ github.event.inputs.version_number }}
file_path: "./src/iOS/Info.plist" file_path: "./src/iOS/Info.plist"
- name: Setup git - name: Setup git
run: | run: |
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com" git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --local user.name "bitwarden-devops-bot" git config --local user.name "github-actions[bot]"
- name: Check if version changed - name: Check if version changed
id: version-changed id: version-changed
run: | run: |
if [ -n "$(git status --porcelain)" ]; then if [ -n "$(git status --porcelain)" ]; then
echo "changes_to_commit=TRUE" >> $GITHUB_OUTPUT echo "::set-output name=changes_to_commit::TRUE"
else else
echo "changes_to_commit=FALSE" >> $GITHUB_OUTPUT echo "::set-output name=changes_to_commit::FALSE"
echo "No changes to commit!"; echo "No changes to commit!";
fi fi
- name: Commit files - name: Commit files
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
run: git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a run: |
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
- name: Push changes - name: Push changes
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }} if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}

View File

@@ -8,4 +8,4 @@ on:
jobs: jobs:
call-workflow: call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@34ecb67b2a357795dc893549df0795e7383ff50f uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master

129
.gitignore vendored
View File

@@ -30,7 +30,6 @@ Components/
[Rr]eleases/ [Rr]eleases/
x64/ x64/
x86/ x86/
!src/lib/x86/
build/ build/
bld/ bld/
[Bb]in/ [Bb]in/
@@ -209,130 +208,4 @@ FakesAssemblies/
# Other # Other
project.lock.json project.lock.json
.DS_Store .DS_Store
src/App/Css src/App/Css
tools
# Created by https://www.toptal.com/developers/gitignore/api/swift,objective-c
# Edit at https://www.toptal.com/developers/gitignore?templates=swift,objective-c
### Objective-C ###
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
*.xcscmblueprint
*.xccheckout
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
build/
DerivedData/
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
# CocoaPods
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
# Pods/
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build/
# fastlane
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
iOSInjectionProject/
### Objective-C Patch ###
### Swift ###
# Xcode
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm
.build/
# CocoaPods
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
# Pods/
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
# Accio dependency management
Dependencies/
.accio/
# fastlane
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
# Code Injection
# After new code Injection tools there's a generated folder /iOSInjectionProject
# https://github.com/johnno1962/injectionforxcode
# End of https://www.toptal.com/developers/gitignore/api/swift,objective-c

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "lib/MessagePack"]
path = lib/MessagePack
url = https://github.com/bitwarden/MessagePack.git

View File

@@ -4,7 +4,7 @@
# Bitwarden Mobile Application # Bitwarden Mobile Application
<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 F-Droid" 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://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>
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.
@@ -12,7 +12,7 @@ The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin
# Build/Run # Build/Run
Please refer to the [Mobile section](https://contributing.bitwarden.com/getting-started/clients/mobile/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started. Please refer to the [Mobile section](https://contributing.bitwarden.com/mobile/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
# We're Hiring! # We're Hiring!
@@ -23,3 +23,15 @@ Interested in contributing in a big way? Consider joining our team! We're hiring
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution. Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.
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.
### Dotnet-format
We recently migrated to using dotnet-format as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
1. Check out your local Branch
2. Run `git merge e0efcfbe45b2a27c73e9593bfd7a71fad2aa7a35`
3. Resolve any merge conflicts, commit.
4. Run `dotnet tool run dotnet-format`
5. Commit
6. Run `git merge -Xours 04539af2a66668b6e85476d5cf318c9150ec4357`
7. Push

View File

@@ -1,11 +0,0 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_41_29)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.214 34C70.7096 34 71.1457 34.1883 71.5124 34.555C71.8791 34.9217 72.0674 35.3479 72.0971 35.8534V50.4336H71.0669V51.9453H72.0971V58.0938C72.0971 59.749 71.7701 61.3942 71.1258 63.0295C70.4816 64.6549 69.6788 66.1019 68.7274 67.3706C67.766 68.6293 66.6262 69.8582 65.308 71.0575C63.98 72.2567 62.7609 73.2478 61.6409 74.0407C60.521 74.8237 59.3515 75.5769 58.1324 76.2806C56.9134 76.9843 56.0511 77.4699 55.5357 77.7177C55.0303 77.9655 54.614 78.1538 54.3068 78.2926C54.0788 78.4115 53.8211 78.471 53.5535 78.471C53.2859 78.471 53.0282 78.4115 52.8003 78.2926C52.5297 78.1791 52.1822 78.0118 51.7511 77.8042C51.6927 77.7761 51.6328 77.7473 51.5713 77.7177C51.0559 77.46 50.1937 76.9843 48.9746 76.2806C47.7555 75.5769 46.586 74.8336 45.4661 74.0407C44.3461 73.2478 43.1172 72.2567 41.799 71.0575C40.4709 69.8682 39.3311 68.6392 38.3797 67.3706C37.4183 66.1119 36.6155 64.6648 35.9713 63.0295C35.3271 61.4041 35 59.749 35 58.0938V35.8534C35 35.3479 35.1883 34.9217 35.555 34.555C35.9217 34.1883 36.3479 34 36.8534 34H70.214ZM67.211 57.5H70.1118V59H67.177C66.4282 66.7468 53.5337 73.2875 53.5337 73.2875V38.7573H67.211V50.4336H70.1118V51.9219H67.211V53.8027H69.895V55.291H67.211V57.5Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M58 46C56.3431 46 55 47.3431 55 49V60C55 61.6569 56.3431 63 58 63H107C108.657 63 110 61.6569 110 60V49C110 47.3431 108.657 46 107 46H58ZM59.7817 50.4336H57.1157V59H60.3208C60.9692 59 61.5278 58.9023 61.9966 58.707C62.4692 58.5078 62.8325 58.2227 63.0864 57.8516C63.3403 57.4805 63.4673 57.0352 63.4673 56.5156C63.4673 56.0664 63.397 55.707 63.2563 55.4375C63.1196 55.1641 62.936 54.957 62.7056 54.8164C62.4751 54.6719 62.2173 54.5703 61.9321 54.5117V54.4531C62.2134 54.4023 62.4517 54.293 62.647 54.125C62.8423 53.957 62.9907 53.7422 63.0923 53.4805C63.1978 53.2188 63.2505 52.9258 63.2505 52.6016C63.2505 51.7969 62.9575 51.2344 62.3716 50.9141C61.7856 50.5938 60.9224 50.4336 59.7817 50.4336ZM59.9868 53.8262H58.9321V51.9219H59.8872C60.4067 51.9219 60.7856 51.9941 61.0239 52.1387C61.2661 52.2793 61.3872 52.5137 61.3872 52.8418C61.3872 53.166 61.2856 53.4121 61.0825 53.5801C60.8794 53.7441 60.5142 53.8262 59.9868 53.8262ZM58.9321 57.5V55.2676H60.0571C60.4438 55.2676 60.7466 55.3125 60.9653 55.4023C61.188 55.4922 61.3462 55.6172 61.4399 55.7773C61.5337 55.9375 61.5806 56.123 61.5806 56.334C61.5806 56.6895 61.4731 56.9727 61.2583 57.1836C61.0435 57.3945 60.6626 57.5 60.1157 57.5H58.9321ZM65.1782 59H70.1118V57.5H66.9946V55.291H69.895V53.8027H66.9946V51.9219H70.1118V50.4336H65.1782V59ZM73.3931 59H75.2095V51.9453H77.5356V50.4336H71.0669V51.9453H73.3931V59ZM83.4771 56.9609L84.0981 59H86.0552L83.02 50.3984H80.7993L77.7759 59H79.7329L80.354 56.9609H83.4771ZM82.4224 53.4453L83.0435 55.4375H80.811L81.4263 53.4453C81.4536 53.3555 81.4985 53.2051 81.561 52.9941C81.6235 52.7832 81.688 52.5605 81.7544 52.3262C81.8247 52.0879 81.8794 51.8887 81.9185 51.7285C81.9575 51.8887 82.0083 52.0781 82.0708 52.2969C82.1372 52.5117 82.2017 52.7246 82.2642 52.9355C82.3306 53.1426 82.3833 53.3125 82.4224 53.4453Z" fill="#6795E8"/>
</g>
<defs>
<clipPath id="clip0_41_29">
<rect width="108" height="108" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -1,17 +0,0 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_36_10)">
<path d="M71.5717 34.6343L113 76.0625L89.709 119.077L40.6492 70.0168L53.5336 77.4501L70.8779 60.1057L71.5717 34.6343Z" fill="url(#paint0_linear_36_10)"/>
<path d="M71.5124 34.555C71.1457 34.1883 70.7096 34 70.214 34H36.8534C36.3479 34 35.9217 34.1883 35.555 34.555C35.1883 34.9217 35 35.3479 35 35.8534V58.0938C35 59.749 35.3271 61.4041 35.9713 63.0295C36.6155 64.6648 37.4183 66.1119 38.3797 67.3706C39.3311 68.6392 40.4709 69.8682 41.799 71.0575C43.1172 72.2567 44.3461 73.2478 45.4661 74.0407C46.586 74.8336 47.7555 75.5769 48.9746 76.2806C50.1937 76.9843 51.0559 77.46 51.5713 77.7177C52.0867 77.9655 52.493 78.1637 52.8003 78.2926C53.0282 78.4115 53.2859 78.471 53.5535 78.471C53.8211 78.471 54.0788 78.4115 54.3068 78.2926C54.614 78.1538 55.0303 77.9655 55.5357 77.7177C56.0511 77.4699 56.9134 76.9843 58.1324 76.2806C59.3515 75.5769 60.521 74.8237 61.6409 74.0407C62.7609 73.2478 63.98 72.2567 65.308 71.0575C66.6262 69.8582 67.766 68.6293 68.7274 67.3706C69.6788 66.1019 70.4816 64.6549 71.1258 63.0295C71.7701 61.3942 72.0971 59.749 72.0971 58.0938V35.8534C72.0674 35.3479 71.8791 34.9217 71.5124 34.555ZM67.211 58.3019C67.211 66.3497 53.5337 73.2875 53.5337 73.2875V38.7573H67.211C67.211 38.7573 67.211 50.2542 67.211 58.3019Z" fill="white"/>
<path d="M55 49C55 47.3431 56.3431 46 58 46H107C108.657 46 110 47.3431 110 49V60C110 61.6569 108.657 63 107 63H58C56.3431 63 55 61.6569 55 60V49Z" fill="#6795E8"/>
<path d="M57.116 50.4336H59.782C60.9226 50.4336 61.7859 50.5938 62.3718 50.9141C62.9578 51.2344 63.2507 51.7969 63.2507 52.6016C63.2507 52.9258 63.198 53.2188 63.0925 53.4805C62.991 53.7422 62.8425 53.957 62.6472 54.125C62.4519 54.293 62.2136 54.4023 61.9324 54.4531V54.5117C62.2175 54.5703 62.4753 54.6719 62.7058 54.8164C62.9363 54.957 63.1199 55.1641 63.2566 55.4375C63.3972 55.707 63.4675 56.0664 63.4675 56.5156C63.4675 57.0352 63.3406 57.4805 63.0867 57.8516C62.8328 58.2227 62.4695 58.5078 61.9968 58.707C61.5281 58.9023 60.9695 59 60.321 59H57.116V50.4336ZM58.9324 53.8262H59.9871C60.5144 53.8262 60.8796 53.7441 61.0828 53.5801C61.2859 53.4121 61.3875 53.166 61.3875 52.8418C61.3875 52.5137 61.2664 52.2793 61.0242 52.1387C60.7859 51.9941 60.407 51.9219 59.8875 51.9219H58.9324V53.8262ZM58.9324 55.2676V57.5H60.116C60.6628 57.5 61.0437 57.3945 61.2585 57.1836C61.4734 56.9727 61.5808 56.6895 61.5808 56.334C61.5808 56.123 61.5339 55.9375 61.4402 55.7773C61.3464 55.6172 61.1882 55.4922 60.9656 55.4023C60.7468 55.3125 60.4441 55.2676 60.0574 55.2676H58.9324ZM70.1121 59H65.1785V50.4336H70.1121V51.9219H66.9949V53.8027H69.8953V55.291H66.9949V57.5H70.1121V59ZM75.2097 59H73.3933V51.9453H71.0671V50.4336H77.5359V51.9453H75.2097V59ZM84.0984 59L83.4773 56.9609H80.3542L79.7332 59H77.7761L80.7996 50.3984H83.0203L86.0554 59H84.0984ZM83.0437 55.4375L82.4226 53.4453C82.3835 53.3125 82.3308 53.1426 82.2644 52.9355C82.2019 52.7246 82.1375 52.5117 82.071 52.2969C82.0085 52.0781 81.9578 51.8887 81.9187 51.7285C81.8796 51.8887 81.825 52.0879 81.7546 52.3262C81.6882 52.5605 81.6238 52.7832 81.5613 52.9941C81.4988 53.2051 81.4539 53.3555 81.4265 53.4453L80.8113 55.4375H83.0437Z" fill="#212529"/>
</g>
<defs>
<linearGradient id="paint0_linear_36_10" x1="37.8512" y1="38.8122" x2="89.011" y2="89.972" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0.247059"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_36_10">
<rect width="108" height="108" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -1,11 +0,0 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_41_21)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.214 34C70.7096 34 71.1457 34.1883 71.5124 34.555C71.8791 34.9217 72.0674 35.3479 72.0971 35.8534V50.4336H71.647L72.0971 51.7604V58.0938C72.0971 59.749 71.7701 61.3942 71.1258 63.0295C70.4816 64.6549 69.6788 66.1019 68.7274 67.3706C67.766 68.6293 66.6262 69.8582 65.308 71.0575C63.98 72.2567 62.7609 73.2478 61.6409 74.0407C60.521 74.8237 59.3515 75.5769 58.1324 76.2806C56.9134 76.9843 56.0511 77.4699 55.5357 77.7177C55.0303 77.9655 54.614 78.1538 54.3068 78.2926C54.0788 78.4115 53.8211 78.471 53.5535 78.471C53.2859 78.471 53.0282 78.4115 52.8003 78.2926C52.5297 78.1791 52.1822 78.0118 51.7511 77.8042C51.6927 77.7761 51.6328 77.7473 51.5713 77.7177C51.0559 77.46 50.1937 76.9843 48.9746 76.2806C47.7555 75.5769 46.586 74.8336 45.4661 74.0407C44.3461 73.2478 43.1172 72.2567 41.799 71.0575C40.4709 69.8682 39.3311 68.6392 38.3797 67.3706C37.4183 66.1119 36.6155 64.6648 35.9713 63.0295C35.3271 61.4041 35 59.749 35 58.0938V35.8534C35 35.3479 35.1883 34.9217 35.555 34.555C35.9217 34.1883 36.3479 34 36.8534 34H70.214ZM67.177 59C66.4282 66.7468 53.5337 73.2875 53.5337 73.2875V38.7573H67.211V50.4336H70.9321V51.9219H67.8149V53.8027H70.7153V55.291H67.8149V57.5H70.9321V59H67.177Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M58 46C56.3431 46 55 47.3431 55 49V60C55 61.6569 56.3431 63 58 63H107C108.657 63 110 61.6569 110 60V49C110 47.3431 108.657 46 107 46H58ZM63.6665 57.0547C64.0376 56.4062 64.2231 55.5996 64.2231 54.6348C64.2231 53.7168 64.0415 52.9473 63.6782 52.3262C63.3149 51.7012 62.8032 51.2305 62.1431 50.9141C61.4829 50.5938 60.7036 50.4336 59.8052 50.4336H57.1157V59H59.5415C60.5259 59 61.3677 58.8379 62.0669 58.5137C62.7661 58.1855 63.2993 57.6992 63.6665 57.0547ZM62.0552 53.123C62.2427 53.5293 62.3364 54.0488 62.3364 54.6816C62.3364 55.6152 62.1196 56.3184 61.686 56.791C61.2563 57.2637 60.5981 57.5 59.7114 57.5H58.9321V51.9219H59.8989C60.4302 51.9219 60.8755 52.0195 61.2349 52.2148C61.5981 52.4102 61.8716 52.7129 62.0552 53.123ZM65.9985 59H70.9321V57.5H67.8149V55.291H70.7153V53.8027H67.8149V51.9219H70.9321V50.4336H65.9985V59ZM76.5337 59L79.4458 50.4336H77.6118L75.9888 55.5312C75.9614 55.6211 75.9165 55.7852 75.854 56.0234C75.7954 56.2578 75.7349 56.5059 75.6724 56.7676C75.6138 57.0293 75.5728 57.2461 75.5493 57.418C75.5259 57.2461 75.481 57.0293 75.4146 56.7676C75.3521 56.502 75.2896 56.252 75.2271 56.0176C75.1646 55.7793 75.1196 55.6172 75.0923 55.5312L73.481 50.4336H71.647L74.5532 59H76.5337Z" fill="#2DA49D"/>
</g>
<defs>
<clipPath id="clip0_41_21">
<rect width="108" height="108" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1,17 +0,0 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_36_2)">
<path d="M71.5717 34.6343L113 76.0625L89.709 119.077L40.6492 70.0168L53.5336 77.4501L70.8779 60.1057L71.5717 34.6343Z" fill="url(#paint0_linear_36_2)"/>
<path d="M71.5124 34.555C71.1457 34.1883 70.7096 34 70.214 34H36.8534C36.3479 34 35.9217 34.1883 35.555 34.555C35.1883 34.9217 35 35.3479 35 35.8534V58.0938C35 59.749 35.3271 61.4041 35.9713 63.0295C36.6155 64.6648 37.4183 66.1119 38.3797 67.3706C39.3311 68.6392 40.4709 69.8682 41.799 71.0575C43.1172 72.2567 44.3461 73.2478 45.4661 74.0407C46.586 74.8336 47.7555 75.5769 48.9746 76.2806C50.1937 76.9843 51.0559 77.46 51.5713 77.7177C52.0867 77.9655 52.493 78.1637 52.8003 78.2926C53.0282 78.4115 53.2859 78.471 53.5535 78.471C53.8211 78.471 54.0788 78.4115 54.3068 78.2926C54.614 78.1538 55.0303 77.9655 55.5357 77.7177C56.0511 77.4699 56.9134 76.9843 58.1324 76.2806C59.3515 75.5769 60.521 74.8237 61.6409 74.0407C62.7609 73.2478 63.98 72.2567 65.308 71.0575C66.6262 69.8582 67.766 68.6293 68.7274 67.3706C69.6788 66.1019 70.4816 64.6549 71.1258 63.0295C71.7701 61.3942 72.0971 59.749 72.0971 58.0938V35.8534C72.0674 35.3479 71.8791 34.9217 71.5124 34.555ZM67.211 58.3019C67.211 66.3497 53.5337 73.2875 53.5337 73.2875V38.7573H67.211C67.211 38.7573 67.211 50.2542 67.211 58.3019Z" fill="white"/>
<path d="M55 49C55 47.3431 56.3431 46 58 46H107C108.657 46 110 47.3431 110 49V60C110 61.6569 108.657 63 107 63H58C56.3431 63 55 61.6569 55 60V49Z" fill="#2DA49D"/>
<path d="M64.2234 54.6348C64.2234 55.5996 64.0378 56.4062 63.6667 57.0547C63.2996 57.6992 62.7664 58.1855 62.0671 58.5137C61.3679 58.8379 60.5261 59 59.5417 59H57.116V50.4336H59.8054C60.7039 50.4336 61.4832 50.5938 62.1433 50.9141C62.8035 51.2305 63.3152 51.7012 63.6785 52.3262C64.0417 52.9473 64.2234 53.7168 64.2234 54.6348ZM62.3367 54.6816C62.3367 54.0488 62.2429 53.5293 62.0554 53.123C61.8718 52.7129 61.5984 52.4102 61.2351 52.2148C60.8757 52.0195 60.4304 51.9219 59.8992 51.9219H58.9324V57.5H59.7117C60.5984 57.5 61.2566 57.2637 61.6863 56.791C62.1199 56.3184 62.3367 55.6152 62.3367 54.6816ZM70.9324 59H65.9988V50.4336H70.9324V51.9219H67.8152V53.8027H70.7156V55.291H67.8152V57.5H70.9324V59ZM79.446 50.4336L76.5339 59H74.5535L71.6472 50.4336H73.4812L75.0925 55.5312C75.1199 55.6172 75.1648 55.7793 75.2273 56.0176C75.2898 56.252 75.3523 56.502 75.4148 56.7676C75.4812 57.0293 75.5261 57.2461 75.5496 57.418C75.573 57.2461 75.614 57.0293 75.6726 56.7676C75.7351 56.5059 75.7957 56.2578 75.8542 56.0234C75.9167 55.7852 75.9617 55.6211 75.989 55.5312L77.6121 50.4336H79.446Z" fill="#212529"/>
</g>
<defs>
<linearGradient id="paint0_linear_36_2" x1="37.8512" y1="38.8122" x2="89.011" y2="89.972" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0.247059"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_36_2">
<rect width="108" height="108" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,11 +0,0 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_41_13)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.214 34C70.7096 34 71.1457 34.1883 71.5124 34.555C71.8791 34.9217 72.0674 35.3479 72.0971 35.8534V52.9823L70.8325 49.3984H68.6118L67.211 53.3838V38.7573H53.5337V73.2875C53.5337 73.2875 67.211 66.3497 67.211 58.3019V58H67.5454L68.1665 55.9609H71.2896L71.9106 58H72.0971V58.0938C72.0971 59.749 71.7701 61.3942 71.1258 63.0295C70.4816 64.6549 69.6788 66.1019 68.7274 67.3706C67.766 68.6293 66.6262 69.8582 65.308 71.0575C63.98 72.2567 62.7609 73.2478 61.6409 74.0407C60.521 74.8237 59.3515 75.5769 58.1324 76.2806C56.9134 76.9843 56.0511 77.4699 55.5357 77.7177C55.0303 77.9655 54.614 78.1538 54.3068 78.2926C54.0788 78.4115 53.8211 78.471 53.5535 78.471C53.2859 78.471 53.0282 78.4115 52.8003 78.2926C52.5297 78.1791 52.1822 78.0118 51.7511 77.8042C51.6927 77.7761 51.6328 77.7473 51.5713 77.7177C51.0559 77.46 50.1937 76.9843 48.9746 76.2806C47.7555 75.5769 46.586 74.8336 45.4661 74.0407C44.3461 73.2478 43.1172 72.2567 41.799 71.0575C40.4709 69.8682 39.3311 68.6392 38.3797 67.3706C37.4183 66.1119 36.6155 64.6648 35.9713 63.0295C35.3271 61.4041 35 59.749 35 58.0938V35.8534C35 35.3479 35.1883 34.9217 35.555 34.555C35.9217 34.1883 36.3479 34 36.8534 34H70.214ZM70.2349 52.4453L70.856 54.4375H68.6235L69.2388 52.4453C69.2661 52.3555 69.311 52.2051 69.3735 51.9941C69.436 51.7832 69.5005 51.5605 69.5669 51.3262C69.6372 51.0879 69.6919 50.8887 69.731 50.7285C69.77 50.8887 69.8208 51.0781 69.8833 51.2969C69.9497 51.5117 70.0142 51.7246 70.0767 51.9355C70.1431 52.1426 70.1958 52.3125 70.2349 52.4453Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M58 46C56.3431 46 55 47.3431 55 49V60C55 61.6569 56.3431 63 58 63H107C108.657 63 110 61.6569 110 60V49C110 47.3431 108.657 46 107 46H58ZM64.6626 55.457C64.8149 54.9258 64.8911 54.3418 64.8911 53.7051C64.8911 52.8145 64.7446 52.0391 64.4517 51.3789C64.1626 50.7188 63.7173 50.207 63.1157 49.8438C62.5181 49.4805 61.7544 49.2988 60.8247 49.2988C59.8911 49.2988 59.1216 49.4805 58.5161 49.8438C57.9106 50.207 57.4614 50.7188 57.1685 51.3789C56.8794 52.0352 56.7349 52.8066 56.7349 53.6934C56.7349 54.3574 56.8169 54.9609 56.981 55.5039C57.145 56.0469 57.3931 56.5137 57.7251 56.9043C58.061 57.2949 58.4849 57.5957 58.9966 57.8066C59.5083 58.0137 60.1138 58.1172 60.813 58.1172H60.8774H60.9478L62.5181 60.0391H64.8442L62.7817 57.7363C63.2622 57.5176 63.6587 57.2148 63.9712 56.8281C64.2837 56.4414 64.5142 55.9844 64.6626 55.457ZM58.8618 55.252C58.7134 54.8184 58.6392 54.3027 58.6392 53.7051C58.6392 53.1035 58.7134 52.5879 58.8618 52.1582C59.0142 51.7246 59.2505 51.3926 59.5708 51.1621C59.895 50.9277 60.313 50.8105 60.8247 50.8105C61.5942 50.8105 62.147 51.0684 62.4829 51.584C62.8188 52.0996 62.9868 52.8066 62.9868 53.7051C62.9868 54.3027 62.9126 54.8184 62.7642 55.252C62.6196 55.6816 62.3872 56.0137 62.0669 56.248C61.7466 56.4785 61.3286 56.5938 60.813 56.5938C60.3052 56.5938 59.8911 56.4785 59.5708 56.248C59.2505 56.0137 59.0142 55.6816 58.8618 55.252ZM71.2896 55.9609L71.9106 58H73.8677L70.8325 49.3984H68.6118L65.5884 58H67.5454L68.1665 55.9609H71.2896ZM70.2349 52.4453L70.856 54.4375H68.6235L69.2388 52.4453C69.2661 52.3555 69.311 52.2051 69.3735 51.9941C69.436 51.7832 69.5005 51.5605 69.5669 51.3262C69.6372 51.0879 69.6919 50.8887 69.731 50.7285C69.77 50.8887 69.8208 51.0781 69.8833 51.2969C69.9497 51.5117 70.0142 51.7246 70.0767 51.9355C70.1431 52.1426 70.1958 52.3125 70.2349 52.4453Z" fill="#C32998"/>
</g>
<defs>
<clipPath id="clip0_41_13">
<rect width="108" height="108" rx="34" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -1,17 +0,0 @@
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_34_2)">
<path d="M71.5717 34.6343L113 76.0625L89.709 119.077L40.6492 70.0168L53.5336 77.4501L70.8779 60.1057L71.5717 34.6343Z" fill="url(#paint0_linear_34_2)"/>
<path d="M71.5124 34.555C71.1457 34.1883 70.7096 34 70.214 34H36.8534C36.3479 34 35.9217 34.1883 35.555 34.555C35.1883 34.9217 35 35.3479 35 35.8534V58.0938C35 59.749 35.3271 61.4041 35.9713 63.0295C36.6155 64.6648 37.4183 66.1119 38.3797 67.3706C39.3311 68.6392 40.4709 69.8682 41.799 71.0575C43.1172 72.2567 44.3461 73.2478 45.4661 74.0407C46.586 74.8336 47.7555 75.5769 48.9746 76.2806C50.1937 76.9843 51.0559 77.46 51.5713 77.7177C52.0867 77.9655 52.493 78.1637 52.8003 78.2926C53.0282 78.4115 53.2859 78.471 53.5535 78.471C53.8211 78.471 54.0788 78.4115 54.3068 78.2926C54.614 78.1538 55.0303 77.9655 55.5357 77.7177C56.0511 77.4699 56.9134 76.9843 58.1324 76.2806C59.3515 75.5769 60.521 74.8237 61.6409 74.0407C62.7609 73.2478 63.98 72.2567 65.308 71.0575C66.6262 69.8582 67.766 68.6293 68.7274 67.3706C69.6788 66.1019 70.4816 64.6549 71.1258 63.0295C71.7701 61.3942 72.0971 59.749 72.0971 58.0938V35.8534C72.0674 35.3479 71.8791 34.9217 71.5124 34.555ZM67.211 58.3019C67.211 66.3497 53.5337 73.2875 53.5337 73.2875V38.7573H67.211C67.211 38.7573 67.211 50.2542 67.211 58.3019Z" fill="white"/>
<path d="M55 49C55 47.3431 56.3431 46 58 46H107C108.657 46 110 47.3431 110 49V60C110 61.6569 108.657 63 107 63H58C56.3431 63 55 61.6569 55 60V49Z" fill="#C32998"/>
<path d="M64.8914 53.7051C64.8914 54.3418 64.8152 54.9258 64.6628 55.457C64.5144 55.9844 64.2839 56.4414 63.9714 56.8281C63.6589 57.2148 63.2625 57.5176 62.782 57.7363L64.8445 60.0391H62.5183L60.948 58.1172C60.9207 58.1172 60.8972 58.1172 60.8777 58.1172C60.8582 58.1172 60.8367 58.1172 60.8132 58.1172C60.114 58.1172 59.5085 58.0137 58.9968 57.8066C58.4851 57.5957 58.0613 57.2949 57.7253 56.9043C57.3933 56.5137 57.1453 56.0469 56.9812 55.5039C56.8171 54.9609 56.7351 54.3574 56.7351 53.6934C56.7351 52.8066 56.8796 52.0352 57.1687 51.3789C57.4617 50.7188 57.9109 50.207 58.5164 49.8438C59.1218 49.4805 59.8914 49.2988 60.825 49.2988C61.7546 49.2988 62.5183 49.4805 63.116 49.8438C63.7175 50.207 64.1628 50.7188 64.4519 51.3789C64.7449 52.0391 64.8914 52.8145 64.8914 53.7051ZM58.6394 53.7051C58.6394 54.3027 58.7136 54.8184 58.8621 55.252C59.0144 55.6816 59.2507 56.0137 59.571 56.248C59.8914 56.4785 60.3054 56.5938 60.8132 56.5938C61.3289 56.5938 61.7468 56.4785 62.0671 56.248C62.3875 56.0137 62.6199 55.6816 62.7644 55.252C62.9128 54.8184 62.9871 54.3027 62.9871 53.7051C62.9871 52.8066 62.8191 52.0996 62.4832 51.584C62.1472 51.0684 61.5945 50.8105 60.825 50.8105C60.3132 50.8105 59.8953 50.9277 59.571 51.1621C59.2507 51.3926 59.0144 51.7246 58.8621 52.1582C58.7136 52.5879 58.6394 53.1035 58.6394 53.7051ZM71.9109 58L71.2898 55.9609H68.1667L67.5457 58H65.5886L68.6121 49.3984H70.8328L73.8679 58H71.9109ZM70.8562 54.4375L70.2351 52.4453C70.196 52.3125 70.1433 52.1426 70.0769 51.9355C70.0144 51.7246 69.95 51.5117 69.8835 51.2969C69.821 51.0781 69.7703 50.8887 69.7312 50.7285C69.6921 50.8887 69.6375 51.0879 69.5671 51.3262C69.5007 51.5605 69.4363 51.7832 69.3738 51.9941C69.3113 52.2051 69.2664 52.3555 69.239 52.4453L68.6238 54.4375H70.8562Z" fill="white"/>
</g>
<defs>
<linearGradient id="paint0_linear_34_2" x1="37.8512" y1="38.8122" x2="89.011" y2="89.972" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0.247059"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_34_2">
<rect width="108" height="108" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -1,136 +0,0 @@
#! /bin/sh
function print_example() {
echo "Example"
echo " icons ios ~/AppIcon.pdf ~/Icons/"
}
function print_usage() {
echo "Usage"
echo " icons <ios|watch|complication|macos> in-file.pdf (out-dir)"
}
function command_exists() {
if type "$1" >/dev/null 2>&1; then
return 1
else
return 0
fi
}
if command_exists "sips" == 0 ; then
echo "sips tool not found"
exit 1
fi
if [ "$1" = "--help" ] || [ "$1" = "-h" ] ; then
print_usage
exit 0
fi
PLATFORM="$1"
FILE="$2"
if [ -z "$PLATFORM" ] || [ -z "$FILE" ] ; then
echo "Error: missing arguments"
echo ""
print_usage
echo ""
print_example
exit 1
fi
DIR="$3"
if [ -z "$DIR" ] ; then
DIR=$(dirname $FILE)
fi
# Create directory if needed
mkdir -p "$DIR"
if [[ "$PLATFORM" == *"ios"* ]] ; then # iOS
sips -s format png -Z '180' "${FILE}" --out "${DIR}"/Icon-180.png
sips -s format png -Z '29' "${FILE}" --out "${DIR}"/Icon-29.png
sips -s format png -Z '58' "${FILE}" --out "${DIR}"/Icon-58.png
sips -s format png -Z '120' "${FILE}" --out "${DIR}"/Icon-120.png
sips -s format png -Z '87' "${FILE}" --out "${DIR}"/Icon-87.png
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/Icon-40.png
sips -s format png -Z '80' "${FILE}" --out "${DIR}"/Icon-80.png
sips -s format png -Z '76' "${FILE}" --out "${DIR}"/Icon-76.png
sips -s format png -Z '152' "${FILE}" --out "${DIR}"/Icon-152.png
sips -s format png -Z '167' "${FILE}" --out "${DIR}"/Icon-167.png
sips -s format png -Z '60' "${FILE}" --out "${DIR}"/Icon-60.png
sips -s format png -Z '20' "${FILE}" --out "${DIR}"/Icon-20.png
sips -s format png -Z '1024' "${FILE}" --out "${DIR}"/Icon-1024.png
# https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/AppIconType.html
contents_json='{"images":[{"size":"20x20","idiom":"iphone","filename":"iPhoneNotification@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"iPhoneNotification@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"iPhoneSettings@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"iPhoneSettings@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"iPhoneSpotlight@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"iPhoneSpotlight@3x.png","scale":"3x"},{"size":"60x60","idiom":"iphone","filename":"iPhone@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"iPhone@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"iPadNotification.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"iPadNotification@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"iPadSettings.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"iPadSettings@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"iPadSpotlight.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"iPadSpotlight@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"iPad.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"iPad@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"iPadPro@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"AppStoreMarketing.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}'
echo $contents_json > "${DIR}"/Contents.json
fi
if [[ "$PLATFORM" == *"watch"* ]] ; then # Apple Watch
sips -s format png -Z '48' "${FILE}" --out "${DIR}"/Watch38mmNotificationCenter.png
sips -s format png -Z '55' "${FILE}" --out "${DIR}"/Watch42mmNotificationCenter.png
sips -s format png -Z '66' "${FILE}" --out "${DIR}"/Watch66NotificationCenter.png
sips -s format png -Z '58' "${FILE}" --out "${DIR}"/WatchCompanionSettings@2x.png
sips -s format png -Z '87' "${FILE}" --out "${DIR}"/WatchCompanionSettings@3x.png
sips -s format png -Z '80' "${FILE}" --out "${DIR}"/Watch38MM42MMHomeScreen.png
sips -s format png -Z '88' "${FILE}" --out "${DIR}"/Watch40MMHomeScreen.png
sips -s format png -Z '92' "${FILE}" --out "${DIR}"/Watch41MMHomeScreen.png
sips -s format png -Z '100' "${FILE}" --out "${DIR}"/Watch44MMHomeScreen.png
sips -s format png -Z '102' "${FILE}" --out "${DIR}"/Watch45MMHomeScreen.png
sips -s format png -Z '108' "${FILE}" --out "${DIR}"/Watch49MMHomeScreen.png
sips -s format png -Z '172' "${FILE}" --out "${DIR}"/Watch38MMShortLook.png
sips -s format png -Z '196' "${FILE}" --out "${DIR}"/Watch40MM42MMShortLook.png
sips -s format png -Z '216' "${FILE}" --out "${DIR}"/Watch44MMShortLook.png
sips -s format png -Z '234' "${FILE}" --out "${DIR}"/Watch234ShortLook.png
sips -s format png -Z '258' "${FILE}" --out "${DIR}"/Watch258ShortLook.png
sips -s format png -Z '1024' "${FILE}" --out "${DIR}"/WatchAppStore.png
# https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/AppIconType.html
contents_json='{"images":[{"size":"24x24","idiom":"watch","scale":"2x","filename":"Watch38mmNotificationCenter.png","role":"notificationCenter","subtype":"38mm"},{"size":"27.5x27.5","idiom":"watch","scale":"2x","filename":"Watch42mmNotificationCenter.png","role":"notificationCenter","subtype":"42mm"},{"size":"29x29","idiom":"watch","filename":"WatchCompanionSettings@2x.png","role":"companionSettings","scale":"2x"},{"size":"29x29","idiom":"watch","filename":"WatchCompanionSettings@3x.png","role":"companionSettings","scale":"3x"},{"size":"40x40","idiom":"watch","filename":"Watch38MM42MMHomeScreen.png","scale":"2x","role":"appLauncher","subtype":"38mm"},{"size":"44x44","idiom":"watch","scale":"2x","filename":"Watch40MMHomeScreen.png","role":"appLauncher","subtype":"40mm"},{"size":"50x50","idiom":"watch","scale":"2x","filename":"Watch44MMHomeScreen.png","role":"appLauncher","subtype":"44mm"},{"size":"86x86","idiom":"watch","scale":"2x","filename":"Watch38MMShortLook.png","role":"quickLook","subtype":"38mm"},{"size":"98x98","idiom":"watch","scale":"2x","filename":"Watch40MM42MMShortLook.png","role":"quickLook","subtype":"42mm"},{"size":"108x108","idiom":"watch","scale":"2x","filename":"Watch44MMShortLook.png","role":"quickLook","subtype":"44mm"},{"idiom":"watch-marketing","filename":"WatchAppStore.png","size":"1024x1024","scale":"1x"}],"info":{"version":1,"author":"xcode"}}'
echo $contents_json > "${DIR}"/Contents.json
fi
if [[ "$PLATFORM" == *"complication"* ]] ; then # Apple Watch
sips -s format png -Z '32' "${FILE}" --out "${DIR}"/Circular38mm2x.png
sips -s format png -Z '36' "${FILE}" --out "${DIR}"/Circular40mm2x.png
sips -s format png -Z '36' "${FILE}" --out "${DIR}"/Circular42mm2x.png
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/Circular44mm2x.png
sips -s format png -Z '182' "${FILE}" --out "${DIR}"/ExtraLarge38mm2x.png
sips -s format png -Z '203' "${FILE}" --out "${DIR}"/ExtraLarge40mm2x.png
sips -s format png -Z '203' "${FILE}" --out "${DIR}"/ExtraLarge42mm2x.png
sips -s format png -Z '224' "${FILE}" --out "${DIR}"/ExtraLarge44mm2x.png
sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicBezel40mm2x.png
sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicBezel42mm2x.png
sips -s format png -Z '94' "${FILE}" --out "${DIR}"/GraphicBezel44mm2x.png
sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicCircular40mm2x.png
sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicCircular42mm2x.png
sips -s format png -Z '94' "${FILE}" --out "${DIR}"/GraphicCircular44mm2x.png
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/GraphicCorner40mm2x.png
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/GraphicCorner42mm2x.png
sips -s format png -Z '44' "${FILE}" --out "${DIR}"/GraphicCorner44mm2x.png
sips -s format png -Z '52' "${FILE}" --out "${DIR}"/GraphicModular38mm2x.png
sips -s format png -Z '58' "${FILE}" --out "${DIR}"/GraphicModular40mm2x.png
sips -s format png -Z '58' "${FILE}" --out "${DIR}"/GraphicModular42mm2x.png
sips -s format png -Z '64' "${FILE}" --out "${DIR}"/GraphicModular44mm2x.png
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/GraphicUtilitarian38mm2x.png
sips -s format png -Z '44' "${FILE}" --out "${DIR}"/GraphicUtilitarian40mm2x.png
sips -s format png -Z '44' "${FILE}" --out "${DIR}"/GraphicUtilitarian42mm2x.png
sips -s format png -Z '50' "${FILE}" --out "${DIR}"/GraphicUtilitarian44mm2x.png
sips -s format png -Z '206' "${FILE}" --out "${DIR}"/GraphicExtraLarge38mm2x.png
sips -s format png -Z '264' "${FILE}" --out "${DIR}"/GraphicExtraLarge44mm2x.png
echo "NOTE: Graphic Extra Large is not generated since that is not rectangular"
fi
if [[ "$PLATFORM" == *"macos"* ]] ; then # macOS
sips -s format png -Z '1024' "${FILE}" --out "${DIR}"/icon_512x512@2x.png
sips -s format png -Z '512' "${FILE}" --out "${DIR}"/icon_512x512.png
sips -s format png -Z '512' "${FILE}" --out "${DIR}"/icon_256x256@2x.png
sips -s format png -Z '256' "${FILE}" --out "${DIR}"/icon_256x256.png
sips -s format png -Z '256' "${FILE}" --out "${DIR}"/icon_128x128@2x.png
sips -s format png -Z '128' "${FILE}" --out "${DIR}"/icon_128x128.png
sips -s format png -Z '64' "${FILE}" --out "${DIR}"/icon_32x32@2x.png
sips -s format png -Z '32' "${FILE}" --out "${DIR}"/icon_32x32.png
sips -s format png -Z '32' "${FILE}" --out "${DIR}"/icon_16x16@2x.png
sips -s format png -Z '16' "${FILE}" --out "${DIR}"/icon_16x16.png
fi

View File

@@ -1,440 +0,0 @@
#addin nuget:?package=Cake.FileHelpers&version=5.0.0
#addin nuget:?package=Cake.AndroidAppManifest&version=1.1.2
#addin nuget:?package=Cake.Plist&version=0.7.0
#addin nuget:?package=Cake.Incubator&version=7.0.0
#tool dotnet:?package=GitVersion.Tool&version=5.10.3
using Path = System.IO.Path;
using System.Text.RegularExpressions;
var debugScript = Argument<bool>("debugScript", false);
var target = Argument("target", "Default");
var configuration = Argument("configuration", "Release");
var variant = Argument("variant", "dev");
abstract record VariantConfig(
string AppName,
string AndroidPackageName,
string iOSBundleId,
string ApsEnvironment
);
const string BASE_BUNDLE_ID_DROID = "com.x8bit.bitwarden";
const string BASE_BUNDLE_ID_IOS = "com.8bit.bitwarden";
record Dev(): VariantConfig("Bitwarden Dev", $"{BASE_BUNDLE_ID_DROID}.dev", $"{BASE_BUNDLE_ID_IOS}.dev", "development");
record QA(): VariantConfig("Bitwarden QA", $"{BASE_BUNDLE_ID_DROID}.qa", $"{BASE_BUNDLE_ID_IOS}.qa", "development");
record Beta(): VariantConfig("Bitwarden Beta", $"{BASE_BUNDLE_ID_DROID}.beta", $"{BASE_BUNDLE_ID_IOS}.beta", "production");
record Prod(): VariantConfig("Bitwarden", $"{BASE_BUNDLE_ID_DROID}", $"{BASE_BUNDLE_ID_IOS}", "production");
VariantConfig GetVariant() => variant.ToLower() switch{
"qa" => new QA(),
"beta" => new Beta(),
"prod" => new Prod(),
_ => new Dev()
};
GitVersion _gitVersion; //will be set by GetGitInfo task
var _slnPath = Path.Combine(""); //base path used to access files. If build.cake file is moved, just update this
string _androidPackageName = string.Empty; //will be set by UpdateAndroidManifest task
string _iOSVersionName = string.Empty; //will be set by UpdateiOSPlist task
string CreateFeatureBranch(string prevVersionName, GitVersion git) => $"{prevVersionName}-{git.BranchName.Replace("/","-")}";
string GetVersionName(string prevVersionName, VariantConfig buildVariant, GitVersion git) => buildVariant is Prod? prevVersionName : CreateFeatureBranch(prevVersionName, git);
int CreateBuildNumber(int previousNumber) => ++previousNumber;
Task("GetGitInfo")
.Does(()=> {
_gitVersion = GitVersion(new GitVersionSettings());
if(debugScript)
{
Information($"GitVersion Dump:\n{_gitVersion.Dump()}");
}
Information("Git data Load successfully.");
});
#region Android
Task("UpdateAndroidAppIcon")
.Does(()=>{
//TODO we'll implement variant icons later
//manifest.ApplicationIcon = "@mipmap/ic_launcher";
Information($"Updated Androix App Icon with success");
});
Task("UpdateAndroidManifest")
.IsDependentOn("GetGitInfo")
.Does(()=>
{
var buildVariant = GetVariant();
var manifestPath = Path.Combine(_slnPath, "src", "Android", "Properties", "AndroidManifest.xml");
// Cake.AndroidAppManifest doesn't currently enable us to access nested items so, quick (not ideal) fix:
var manifestText = FileReadText(manifestPath);
manifestText = manifestText.Replace("com.x8bit.bitwarden.", buildVariant.AndroidPackageName + ".");
manifestText = manifestText.Replace("android:label=\"Bitwarden\"", $"android:label=\"{buildVariant.AppName}\"");
FileWriteText(manifestPath, manifestText);
var manifest = DeserializeAppManifest(manifestPath);
var prevVersionCode = manifest.VersionCode;
var prevVersionName = manifest.VersionName;
_androidPackageName = manifest.PackageName;
//manifest.VersionCode = CreateBuildNumber(prevVersionCode);
manifest.VersionName = GetVersionName(prevVersionName, buildVariant, _gitVersion);
manifest.PackageName = buildVariant.AndroidPackageName;
manifest.ApplicationLabel = buildVariant.AppName;
//Information($"AndroidManigest.xml VersionCode from {prevVersionCode} to {manifest.VersionCode}");
Information($"AndroidManigest.xml VersionName from {prevVersionName} to {manifest.VersionName}");
Information($"AndroidManigest.xml PackageName from {_androidPackageName} to {buildVariant.AndroidPackageName}");
Information($"AndroidManigest.xml ApplicationLabel to {buildVariant.AppName}");
SerializeAppManifest(manifestPath, manifest);
Information("AndroidManifest updated with success!");
});
void ReplaceInFile(string filePath, string oldtext, string newtext)
{
var fileText = FileReadText(filePath);
if(string.IsNullOrEmpty(fileText) || !fileText.Contains(oldtext))
{
throw new Exception($"Couldn't find {filePath} or it didn't contain: {oldtext}");
}
fileText = fileText.Replace(oldtext, newtext);
FileWriteText(filePath, fileText);
Information($"{filePath} modified successfully.");
}
Task("UpdateAndroidCodeFiles")
.IsDependentOn("UpdateAndroidManifest")
.Does(()=> {
var buildVariant = GetVariant();
//We're not using _androidPackageName here because the codefile is currently slightly different string than the one in AndroidManifest.xml
var keyName = "com.8bit.bitwarden";
var fixedPackageName = buildVariant.AndroidPackageName.Replace("x8bit", "8bit");
var filePath = Path.Combine(_slnPath, "src", "Android", "Services", "BiometricService.cs");
ReplaceInFile(filePath, keyName, fixedPackageName);
var packageFileList = new string[] {
Path.Combine(_slnPath, "src", "Android", "MainActivity.cs"),
Path.Combine(_slnPath, "src", "Android", "MainApplication.cs"),
Path.Combine(_slnPath, "src", "Android", "Constants.cs"),
Path.Combine(_slnPath, "src", "Android", "Accessibility", "AccessibilityService.cs"),
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillHelpers.cs"),
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillService.cs"),
Path.Combine(_slnPath, "src", "Android", "Receivers", "ClearClipboardAlarmReceiver.cs"),
Path.Combine(_slnPath, "src", "Android", "Receivers", "EventUploadReceiver.cs"),
Path.Combine(_slnPath, "src", "Android", "Receivers", "PackageReplacedReceiver.cs"),
Path.Combine(_slnPath, "src", "Android", "Receivers", "RestrictionsChangedReceiver.cs"),
Path.Combine(_slnPath, "src", "Android", "Services", "DeviceActionService.cs"),
Path.Combine(_slnPath, "src", "Android", "Services", "FileService.cs"),
Path.Combine(_slnPath, "src", "Android", "Tiles", "AutofillTileService.cs"),
Path.Combine(_slnPath, "src", "Android", "Tiles", "GeneratorTileService.cs"),
Path.Combine(_slnPath, "src", "Android", "Tiles", "MyVaultTileService.cs"),
Path.Combine(_slnPath, "src", "Android", "google-services.json"),
Path.Combine(_slnPath, "store", "google", "Publisher", "Program.cs"),
};
foreach(string path in packageFileList)
{
ReplaceInFile(path, "com.x8bit.bitwarden", buildVariant.AndroidPackageName);
}
var labelFileList = new string[] {
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillService.cs"),
};
foreach(string path in labelFileList)
{
ReplaceInFile(path, "Bitwarden\"", $"{buildVariant.AppName}\"");
}
});
#endregion Android
#region iOS
enum iOSProjectType
{
Null,
MainApp,
Autofill,
Extension,
ShareExtension,
WatchApp
}
string GetiOSBundleId(VariantConfig buildVariant, iOSProjectType projectType) => projectType switch
{
iOSProjectType.Autofill => $"{buildVariant.iOSBundleId}.autofill",
iOSProjectType.Extension => $"{buildVariant.iOSBundleId}.find-login-action-extension",
iOSProjectType.ShareExtension => $"{buildVariant.iOSBundleId}.share-extension",
iOSProjectType.WatchApp => $"{buildVariant.iOSBundleId}.watchkitapp",
_ => buildVariant.iOSBundleId
};
string GetiOSBundleName(VariantConfig buildVariant, iOSProjectType projectType) => projectType switch
{
iOSProjectType.Autofill => $"{buildVariant.AppName} Autofill",
iOSProjectType.Extension => $"{buildVariant.AppName} Extension",
iOSProjectType.ShareExtension => $"{buildVariant.AppName} Share Extension",
_ => buildVariant.AppName
};
private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, GitVersion git, iOSProjectType projectType = iOSProjectType.MainApp)
{
var plistFile = File(plistPath);
dynamic plist = DeserializePlist(plistFile);
var prevVersionName = plist["CFBundleShortVersionString"];
var prevVersionString = plist["CFBundleVersion"];
var prevVersion = int.Parse(plist["CFBundleVersion"]);
var prevBundleId = plist["CFBundleIdentifier"];
var prevBundleName = plist["CFBundleName"];
//var newVersion = CreateBuildNumber(prevVersion).ToString();
var newVersionName = GetVersionName(prevVersionName, buildVariant, git);
var newBundleId = GetiOSBundleId(buildVariant, projectType);
var newBundleName = GetiOSBundleName(buildVariant, projectType);
plist["CFBundleName"] = newBundleName;
plist["CFBundleDisplayName"] = newBundleName;
//plist["CFBundleVersion"] = newVersion;
plist["CFBundleShortVersionString"] = newVersionName;
plist["CFBundleIdentifier"] = newBundleId;
if(projectType == iOSProjectType.MainApp)
{
_iOSVersionName = newVersionName;
plist["CFBundleURLTypes"][0]["CFBundleURLName"] = $"{buildVariant.iOSBundleId}.url";
}
if(projectType == iOSProjectType.Extension)
{
var keyText = plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"];
plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"] = keyText.Replace("com.8bit.bitwarden", buildVariant.iOSBundleId);
}
SerializePlist(plistFile, plist);
Information($"Changed app name from {prevBundleName} to {newBundleName}");
//Information($"Changed Bundle Version from {prevVersion} to {newVersion}");
Information($"Changed Bundle Short Version name from {prevVersionName} to {newVersionName}");
Information($"Changed Bundle Identifier from {prevBundleId} to {newBundleId}");
Information($"{plistPath} updated with success!");
}
private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig buildVariant)
{
var EntitlementlistFile = File(entitlementsPath);
dynamic Entitlements = DeserializePlist(EntitlementlistFile);
Entitlements["aps-environment"] = buildVariant.ApsEnvironment;
Entitlements["keychain-access-groups"] = new List<string>() { "$(AppIdentifierPrefix)" + buildVariant.iOSBundleId };
Entitlements["com.apple.security.application-groups"] = new List<string>() { $"group.{buildVariant.iOSBundleId}" };;
Information($"Changed ApsEnvironment name to {buildVariant.ApsEnvironment}");
Information($"Changed keychain-access-groups bundleID to {buildVariant.iOSBundleId}");
SerializePlist(EntitlementlistFile, Entitlements);
Information($"{entitlementsPath} updated with success!");
}
private void UpdateWatchKitAppInfoPlist(string plistPath, VariantConfig buildVariant)
{
var plistFile = File(plistPath);
dynamic plist = DeserializePlist(plistFile);
var prevBundleId = plist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"];
var newBundleId = GetiOSBundleId(buildVariant, iOSProjectType.WatchApp);
plist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"] = newBundleId;
SerializePlist(plistFile, plist);
Information($"Changed Bundle Identifier from {prevBundleId} to {newBundleId}");
Information($"{plistPath} updated with success!");
}
private void UpdateWatchPbxproj(string pbxprojPath, string newVersion)
{
var fileText = FileReadText(pbxprojPath);
if (string.IsNullOrEmpty(fileText))
{
throw new Exception($"Couldn't find {pbxprojPath}");
}
const string pattern = @"MARKETING_VERSION = [^;]*;";
fileText = Regex.Replace(fileText, pattern, $"MARKETING_VERSION = {newVersion};");
FileWriteText(pbxprojPath, fileText);
Information($"{pbxprojPath} modified successfully.");
}
/// <summary>
/// Updates the target icons on the given appiconset target
/// taking as source the icon in appIcons/iOS folder for the giving variant
/// </summary>
/// <param name="target">It can be <ios|watch|complication|macos></param>
/// <param name="appiconsetTarget">Folder to copy the generated icons to</param>
private void UpdateAppleIcons(string target, string appiconsetTarget)
{
Information($"Updating {target} App Icons");
var iconsTempDirPath = Path.Combine(_slnPath, "appIcons", "temp");
CreateDirectory(iconsTempDirPath);
var arguments = new ProcessArgumentBuilder();
arguments.Append(target);
arguments.Append(Path.Combine(_slnPath, "appIcons", "iOS", $"{variant}.png"));
arguments.Append(iconsTempDirPath);
using(var process = StartAndReturnProcess(Path.Combine(_slnPath, "appIcons", "icongen.sh"),
new ProcessSettings { Arguments = arguments }))
{
process.WaitForExit();
Information("Exit code: {0}", process.GetExitCode());
}
var generatedIconsPath = Path.Combine(iconsTempDirPath, "*.png");
CopyFiles(generatedIconsPath, appiconsetTarget);
DeleteDirectory(iconsTempDirPath, new DeleteDirectorySettings {
Recursive = true,
Force = true
});
Information($"{target} App Icons have been updated");
}
Task("UpdateiOSIcons")
.Does(()=>{
UpdateAppleIcons("ios", Path.Combine(_slnPath, "src", "iOS", "Resources", "Assets.xcassets", "AppIcons.appiconset"));
UpdateAppleIcons("watch", Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden WatchKit App", "Assets.xcassets", "AppIcon.appiconset"));
// TODO: Update complication icons when they start working
});
Task("UpdateiOSPlist")
.IsDependentOn("GetGitInfo")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "iOS", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.MainApp);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSAutofillPlist")
.IsDependentOn("GetGitInfo")
.IsDependentOn("UpdateiOSPlist")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Autofill);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSExtensionPlist")
.IsDependentOn("GetGitInfo")
.IsDependentOn("UpdateiOSPlist")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Extension);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSShareExtensionPlist")
.IsDependentOn("GetGitInfo")
.IsDependentOn("UpdateiOSPlist")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Info.plist");
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Entitlements.plist");
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.ShareExtension);
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
});
Task("UpdateiOSCodeFiles")
.IsDependentOn("UpdateiOSPlist")
.Does(()=> {
var buildVariant = GetVariant();
var fileList = new string[] {
Path.Combine(_slnPath, "src", "iOS.Core", "Utilities", "iOSCoreHelpers.cs"),
Path.Combine(_slnPath, "src", "iOS.Core", "Constants.cs"),
Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden.xcodeproj", "project.pbxproj"),
Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden WatchKit Extension", "Helpers", "KeychainHelper.swift"),
Path.Combine(".github", "resources", "export-options-ad-hoc.plist"),
Path.Combine(".github", "resources", "export-options-app-store.plist")
};
foreach(string path in fileList)
{
ReplaceInFile(path, "com.8bit.bitwarden", buildVariant.iOSBundleId);
}
});
Task("UpdateWatchProject")
.IsDependentOn("UpdateiOSPlist")
.WithCriteria(() => !string.IsNullOrEmpty(_iOSVersionName))
.Does(()=> {
var watchProjectPath = Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden.xcodeproj", "project.pbxproj");
UpdateWatchPbxproj(watchProjectPath, _iOSVersionName);
});
Task("UpdateWatchKitAppInfoPlist")
.Does(()=> {
var buildVariant = GetVariant();
var infoPath = Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden WatchKit Extension", "Info.plist");
UpdateWatchKitAppInfoPlist(infoPath, buildVariant);
});
#endregion iOS
#region Main Tasks
Task("Android")
//.IsDependentOn("UpdateAndroidAppIcon")
.IsDependentOn("UpdateAndroidManifest")
.IsDependentOn("UpdateAndroidCodeFiles")
.Does(()=>
{
Information("Android app updated");
});
Task("iOS")
.IsDependentOn("UpdateiOSIcons")
.IsDependentOn("UpdateiOSPlist")
.IsDependentOn("UpdateiOSAutofillPlist")
.IsDependentOn("UpdateiOSExtensionPlist")
.IsDependentOn("UpdateiOSShareExtensionPlist")
.IsDependentOn("UpdateiOSCodeFiles")
.IsDependentOn("UpdateWatchProject")
.IsDependentOn("UpdateWatchKitAppInfoPlist")
.Does(()=>
{
Information("iOS app updated");
});
Task("Default")
.Does(() => {
var usage = @"Missing target.
Usage:
dotnet cake build.cake --target (Android | iOS) --variant (dev | qa | beta | prod)
Options:
--debugScript=<bool> Script debug mode.
";
Information(usage);
});
#endregion Main Tasks
RunTarget(target);

View File

@@ -38,15 +38,3 @@ files:
pt-PT: pt-PT pt-PT: pt-PT
en-GB: en-GB en-GB: en-GB
en-IN: en-IN en-IN: en-IN
- source: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/Localizable.strings"
dest: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/%original_file_name%"
translation: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization//%two_letters_code%.lproj/%original_file_name%"
update_option: update_as_unapproved
languages_mapping:
two_letters_code:
zh-CN: zh-Hans
zh-TW: zh-Hant
pt-BR: pt-BR
pt-PT: pt-PT
en-GB: en-GB
en-IN: en-IN

Submodule lib/MessagePack deleted from 1ecb15e311

Binary file not shown.

22
renovate.json Normal file
View File

@@ -0,0 +1,22 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base",
"schedule:monthly",
":maintainLockFilesMonthly",
":preserveSemverRanges",
":rebaseStalePrs",
":disableDependencyDashboard"
],
"enabledManagers": [
"nuget"
],
"packageRules": [
{
"matchManagers": ["nuget"],
"groupName": "Nuget updates",
"groupSlug": "nuget",
"separateMajorMinor": false
}
]
}

View File

@@ -33,7 +33,6 @@ namespace Bit.Droid.Accessibility
// - Resources/xml/autofillservice.xml // - Resources/xml/autofillservice.xml
new Browser("alook.browser", "search_fragment_input_view"), new Browser("alook.browser", "search_fragment_input_view"),
new Browser("alook.browser.google", "search_fragment_input_view"), new Browser("alook.browser.google", "search_fragment_input_view"),
new Browser("app.vanadium.browser", "url_bar"),
new Browser("com.amazon.cloud9", "url"), new Browser("com.amazon.cloud9", "url"),
new Browser("com.android.browser", "url"), new Browser("com.android.browser", "url"),
new Browser("com.android.chrome", "url_bar"), new Browser("com.android.chrome", "url_bar"),
@@ -67,7 +66,6 @@ namespace Bit.Droid.Accessibility
new Browser("com.mmbox.xbrowser", "search_box"), new Browser("com.mmbox.xbrowser", "search_box"),
new Browser("com.mycompany.app.soulbrowser", "edit_text"), new Browser("com.mycompany.app.soulbrowser", "edit_text"),
new Browser("com.naver.whale", "url_bar"), new Browser("com.naver.whale", "url_bar"),
new Browser("com.neeva.app", "full_url_text_view"),
new Browser("com.opera.browser", "url_field"), new Browser("com.opera.browser", "url_field"),
new Browser("com.opera.browser.beta", "url_field"), new Browser("com.opera.browser.beta", "url_field"),
new Browser("com.opera.gx", "addressbarEdit"), new Browser("com.opera.gx", "addressbarEdit"),
@@ -76,7 +74,6 @@ namespace Bit.Droid.Accessibility
new Browser("com.opera.touch", "addressbarEdit"), new Browser("com.opera.touch", "addressbarEdit"),
new Browser("com.qflair.browserq", "url"), 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.qwant.liberty", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v4)
new Browser("com.rainsee.create", "search_box"),
new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"), 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.sec.android.app.sbrowser.beta", "location_bar_edit_text"),
new Browser("com.stoutner.privacybrowser.free", "url_edittext"), new Browser("com.stoutner.privacybrowser.free", "url_edittext"),
@@ -86,9 +83,6 @@ namespace Bit.Droid.Accessibility
new Browser("com.vivaldi.browser.sopranos", "url_bar"), new Browser("com.vivaldi.browser.sopranos", "url_bar"),
new Browser("com.yandex.browser", "bro_omnibar_address_title_text,bro_omnibox_collapsed_title", 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) (s) => s.Split(new char[]{' ', ' '}).FirstOrDefault()), // 0 = Regular Space, 1 = No-break space (00A0)
new Browser("com.yjllq.internet", "search_box"),
new Browser("com.yjllq.kito", "search_box"),
new Browser("com.yujian.ResideMenuDemo", "search_box"),
new Browser("com.z28j.feel", "g2"), new Browser("com.z28j.feel", "g2"),
new Browser("idm.internet.download.manager", "search"), new Browser("idm.internet.download.manager", "search"),
new Browser("idm.internet.download.manager.adm.lite", "search"), new Browser("idm.internet.download.manager.adm.lite", "search"),
@@ -96,7 +90,6 @@ namespace Bit.Droid.Accessibility
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"), new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
new Browser("mark.via", "am,an"), new Browser("mark.via", "am,an"),
new Browser("mark.via.gp", "as"), new Browser("mark.via.gp", "as"),
new Browser("net.dezor.browser", "url_bar"),
new Browser("net.slions.fulguris.full.download", "search"), new Browser("net.slions.fulguris.full.download", "search"),
new Browser("net.slions.fulguris.full.download.debug", "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", "search"),
@@ -136,7 +129,6 @@ namespace Bit.Droid.Accessibility
new Browser("com.htc.sense.browser", "title"), new Browser("com.htc.sense.browser", "title"),
new Browser("com.jerky.browser2", "enterUrl"), new Browser("com.jerky.browser2", "enterUrl"),
new Browser("com.ksmobile.cb", "address_bar_edit_text"), new Browser("com.ksmobile.cb", "address_bar_edit_text"),
new Browser("com.lemurbrowser.exts","url_bar"),
new Browser("com.linkbubble.playstore", "url_text"), new Browser("com.linkbubble.playstore", "url_text"),
new Browser("com.mx.browser", "address_editor_with_progress"), new Browser("com.mx.browser", "address_editor_with_progress"),
new Browser("com.mx.browser.tablet", "address_editor_with_progress"), new Browser("com.mx.browser.tablet", "address_editor_with_progress"),
@@ -375,7 +367,7 @@ namespace Bit.Droid.Accessibility
public static string GetUri(AccessibilityNodeInfo root) public static string GetUri(AccessibilityNodeInfo root)
{ {
var uri = string.Concat(Core.Constants.AndroidAppProtocol, root.PackageName); var uri = string.Concat(Constants.AndroidAppProtocol, root.PackageName);
if (SupportedBrowsers.ContainsKey(root.PackageName)) if (SupportedBrowsers.ContainsKey(root.PackageName))
{ {
var browser = SupportedBrowsers[root.PackageName]; var browser = SupportedBrowsers[root.PackageName];

View File

@@ -15,7 +15,7 @@
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest> <AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix> <MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix> <MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<TargetFrameworkVersion>v13.0</TargetFrameworkVersion> <TargetFrameworkVersion>v12.1</TargetFrameworkVersion>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType> <AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<NuGetPackageImportStamp> <NuGetPackageImportStamp>
</NuGetPackageImportStamp> </NuGetPackageImportStamp>
@@ -77,21 +77,22 @@
<PackageReference Include="Portable.BouncyCastle"> <PackageReference Include="Portable.BouncyCastle">
<Version>1.9.0</Version> <Version>1.9.0</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.5.1.1" /> <PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.5.1" />
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.16" /> <PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.13" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.19" /> <PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.16" />
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.10.0" /> <PackageReference Include="Xamarin.AndroidX.Core" Version="1.9.0" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.3.1.1" /> <PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.14" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.3.1" />
<PackageReference Include="Xamarin.Essentials"> <PackageReference Include="Xamarin.Essentials">
<Version>1.7.5</Version> <Version>1.7.3</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging"> <PackageReference Include="Xamarin.Firebase.Messaging">
<Version>123.1.1.1</Version> <Version>123.0.8</Version>
</PackageReference> </PackageReference>
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.8.0" /> <PackageReference Include="Xamarin.Google.Android.Material" Version="1.6.1.1" />
<PackageReference Include="Xamarin.Google.Dagger" Version="2.44.2.1" /> <PackageReference Include="Xamarin.Google.Dagger" Version="2.41.0.2" />
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet"> <PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
<Version>118.0.1.3</Version> <Version>118.0.1.2</Version>
</PackageReference> </PackageReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -102,10 +103,8 @@
<Compile Include="Accessibility\Browser.cs" /> <Compile Include="Accessibility\Browser.cs" />
<Compile Include="Accessibility\NodeList.cs" /> <Compile Include="Accessibility\NodeList.cs" />
<Compile Include="Accessibility\KnownUsernameField.cs" /> <Compile Include="Accessibility\KnownUsernameField.cs" />
<Compile Include="Autofill\AutofillConstants.cs" />
<Compile Include="Autofill\AutofillHelpers.cs" /> <Compile Include="Autofill\AutofillHelpers.cs" />
<Compile Include="Autofill\AutofillService.cs" /> <Compile Include="Autofill\AutofillService.cs" />
<Compile Include="Autofill\AutofillExternalSelectionActivity.cs" />
<Compile Include="Autofill\Field.cs" /> <Compile Include="Autofill\Field.cs" />
<Compile Include="Autofill\FieldCollection.cs" /> <Compile Include="Autofill\FieldCollection.cs" />
<Compile Include="Autofill\FilledItem.cs" /> <Compile Include="Autofill\FilledItem.cs" />
@@ -153,13 +152,6 @@
<Compile Include="Utilities\IntentExtensions.cs" /> <Compile Include="Utilities\IntentExtensions.cs" />
<Compile Include="Renderers\CustomPageRenderer.cs" /> <Compile Include="Renderers\CustomPageRenderer.cs" />
<Compile Include="Effects\NoEmojiKeyboardEffect.cs" /> <Compile Include="Effects\NoEmojiKeyboardEffect.cs" />
<Compile Include="Receivers\NotificationDismissReceiver.cs" />
<Compile Include="Services\FileService.cs" />
<Compile Include="Services\AutofillHandler.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Effects\RemoveFontPaddingEffect.cs" />
<Compile Include="Services\WatchDeviceService.cs" />
<Compile Include="Renderers\CustomLabelRenderer.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidAsset Include="Assets\bwi-font.ttf" /> <AndroidAsset Include="Assets\bwi-font.ttf" />
@@ -169,10 +161,6 @@
<GoogleServicesJson Include="google-services.json" /> <GoogleServicesJson Include="google-services.json" />
<GoogleServicesJson Include="google-services.json.enc" /> <GoogleServicesJson Include="google-services.json.enc" />
<None Include="fdroid-keystore.jks.enc" /> <None Include="fdroid-keystore.jks.enc" />
<AndroidNativeLibrary Include="lib\arm64-v8a\libargon2.so" />
<AndroidNativeLibrary Include="lib\armeabi-v7a\libargon2.so" />
<AndroidNativeLibrary Include="lib\x86\libargon2.so" />
<AndroidNativeLibrary Include="lib\x86_64\libargon2.so" />
<None Include="Properties\AndroidManifest.xml" /> <None Include="Properties\AndroidManifest.xml" />
<None Include="upload-keystore.jks.enc" /> <None Include="upload-keystore.jks.enc" />
</ItemGroup> </ItemGroup>
@@ -233,18 +221,6 @@
<SubType></SubType> <SubType></SubType>
<Generator></Generator> <Generator></Generator>
</AndroidResource> </AndroidResource>
<AndroidResource Include="Resources\layout\validatable_input_dialog_layout.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
<AndroidResource Include="Resources\drawable\empty_uris_placeholder.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
<AndroidResource Include="Resources\drawable\empty_uris_placeholder_dark.xml">
<SubType></SubType>
<Generator></Generator>
</AndroidResource>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<AndroidResource Include="Resources\drawable\splash_screen.xml" /> <AndroidResource Include="Resources\drawable\splash_screen.xml" />

Binary file not shown.

View File

@@ -1,10 +0,0 @@
namespace Bit.Droid.Autofill
{
public class AutofillConstants
{
public const string AutofillFramework = "autofillFramework";
public const string AutofillFrameworkFillType = "autofillFrameworkFillType";
public const string AutofillFrameworkUri = "autofillFrameworkUri";
public const string AutofillFrameworkCipherId = "autofillFrameworkCipherId";
}
}

View File

@@ -1,42 +0,0 @@
using System.Threading.Tasks;
using Android.App;
using Android.Content.PM;
using Android.OS;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Droid.Utilities;
namespace Bit.Droid.Autofill
{
[Activity(
NoHistory = true,
LaunchMode = LaunchMode.SingleTop)]
public class AutofillExternalSelectionActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
Intent?.Validate();
base.OnCreate(bundle);
var cipherId = Intent?.GetStringExtra(AutofillConstants.AutofillFrameworkCipherId);
if (string.IsNullOrEmpty(cipherId))
{
SetResult(Result.Canceled);
Finish();
return;
}
GetCipherAndPerformAutofillAsync(cipherId).FireAndForget();
}
private async Task GetCipherAndPerformAutofillAsync(string cipherId)
{
var cipherService = ServiceContainer.Resolve<ICipherService>();
var cipher = await cipherService.GetAsync(cipherId);
var decCipher = await cipher.DecryptAsync();
var autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
autofillHandler.Autofill(decCipher);
}
}
}

View File

@@ -54,7 +54,6 @@ namespace Bit.Droid.Autofill
{ {
"alook.browser", "alook.browser",
"alook.browser.google", "alook.browser.google",
"app.vanadium.browser",
"com.amazon.cloud9", "com.amazon.cloud9",
"com.android.browser", "com.android.browser",
"com.android.chrome", "com.android.chrome",
@@ -79,7 +78,6 @@ namespace Bit.Droid.Autofill
"com.jamal2367.styx", "com.jamal2367.styx",
"com.kiwibrowser.browser", "com.kiwibrowser.browser",
"com.kiwibrowser.browser.dev", "com.kiwibrowser.browser.dev",
"com.lemurbrowser.exts",
"com.microsoft.emmx", "com.microsoft.emmx",
"com.microsoft.emmx.beta", "com.microsoft.emmx.beta",
"com.microsoft.emmx.canary", "com.microsoft.emmx.canary",
@@ -88,7 +86,6 @@ namespace Bit.Droid.Autofill
"com.mmbox.xbrowser", "com.mmbox.xbrowser",
"com.mycompany.app.soulbrowser", "com.mycompany.app.soulbrowser",
"com.naver.whale", "com.naver.whale",
"com.neeva.app",
"com.opera.browser", "com.opera.browser",
"com.opera.browser.beta", "com.opera.browser.beta",
"com.opera.gx", "com.opera.gx",
@@ -97,7 +94,6 @@ namespace Bit.Droid.Autofill
"com.opera.touch", "com.opera.touch",
"com.qflair.browserq", "com.qflair.browserq",
"com.qwant.liberty", "com.qwant.liberty",
"com.rainsee.create",
"com.sec.android.app.sbrowser", "com.sec.android.app.sbrowser",
"com.sec.android.app.sbrowser.beta", "com.sec.android.app.sbrowser.beta",
"com.stoutner.privacybrowser.free", "com.stoutner.privacybrowser.free",
@@ -106,9 +102,6 @@ namespace Bit.Droid.Autofill
"com.vivaldi.browser.snapshot", "com.vivaldi.browser.snapshot",
"com.vivaldi.browser.sopranos", "com.vivaldi.browser.sopranos",
"com.yandex.browser", "com.yandex.browser",
"com.yjllq.internet",
"com.yjllq.kito",
"com.yujian.ResideMenuDemo",
"com.z28j.feel", "com.z28j.feel",
"idm.internet.download.manager", "idm.internet.download.manager",
"idm.internet.download.manager.adm.lite", "idm.internet.download.manager.adm.lite",
@@ -116,7 +109,6 @@ namespace Bit.Droid.Autofill
"io.github.forkmaintainers.iceraven", "io.github.forkmaintainers.iceraven",
"mark.via", "mark.via",
"mark.via.gp", "mark.via.gp",
"net.dezor.browser",
"net.slions.fulguris.full.download", "net.slions.fulguris.full.download",
"net.slions.fulguris.full.download.debug", "net.slions.fulguris.full.download.debug",
"net.slions.fulguris.full.playstore", "net.slions.fulguris.full.playstore",
@@ -215,7 +207,7 @@ namespace Bit.Droid.Autofill
} }
} }
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i], var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i],
true, inlinePresentationSpec); inlinePresentationSpec);
if (dataset != null) if (dataset != null)
{ {
responseBuilder.AddDataset(dataset); responseBuilder.AddDataset(dataset);
@@ -229,7 +221,7 @@ namespace Bit.Droid.Autofill
} }
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem, public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem,
bool includeAuthIntent, InlinePresentationSpec inlinePresentationSpec = null) InlinePresentationSpec inlinePresentationSpec = null)
{ {
var overlayPresentation = BuildOverlayPresentation( var overlayPresentation = BuildOverlayPresentation(
filledItem.Name, filledItem.Name,
@@ -250,15 +242,6 @@ namespace Bit.Droid.Autofill
{ {
datasetBuilder.SetInlinePresentation(inlinePresentation); datasetBuilder.SetInlinePresentation(inlinePresentation);
} }
if (includeAuthIntent)
{
var intent = new Intent(context, typeof(AutofillExternalSelectionActivity));
intent.PutExtra(AutofillConstants.AutofillFramework, true);
intent.PutExtra(AutofillConstants.AutofillFrameworkCipherId, filledItem.Id);
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
datasetBuilder.SetAuthentication(pendingIntent?.IntentSender);
}
if (filledItem.ApplyToFields(fields, datasetBuilder)) if (filledItem.ApplyToFields(fields, datasetBuilder))
{ {
return datasetBuilder.Build(); return datasetBuilder.Build();
@@ -270,26 +253,25 @@ namespace Bit.Droid.Autofill
IList<InlinePresentationSpec> inlinePresentationSpecs = null) IList<InlinePresentationSpec> inlinePresentationSpecs = null)
{ {
var intent = new Intent(context, typeof(MainActivity)); var intent = new Intent(context, typeof(MainActivity));
intent.PutExtra(AutofillConstants.AutofillFramework, true); intent.PutExtra("autofillFramework", true);
if (fields.FillableForLogin) if (fields.FillableForLogin)
{ {
intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Login); intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Login);
} }
else if (fields.FillableForCard) else if (fields.FillableForCard)
{ {
intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Card); intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Card);
} }
else if (fields.FillableForIdentity) else if (fields.FillableForIdentity)
{ {
intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Identity); intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Identity);
} }
else else
{ {
return null; return null;
} }
intent.PutExtra(AutofillConstants.AutofillFrameworkUri, uri); intent.PutExtra("autofillFrameworkUri", uri);
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent, var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent, AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
var overlayPresentation = BuildOverlayPresentation( var overlayPresentation = BuildOverlayPresentation(
AppResources.AutofillWithBitwarden, AppResources.AutofillWithBitwarden,

View File

@@ -134,7 +134,7 @@ namespace Bit.Droid.Autofill
{ {
case CipherType.Login: case CipherType.Login:
intent.PutExtra("autofillFrameworkName", parser.Uri intent.PutExtra("autofillFrameworkName", parser.Uri
.Replace(Core.Constants.AndroidAppProtocol, string.Empty) .Replace(Constants.AndroidAppProtocol, string.Empty)
.Replace("https://", string.Empty) .Replace("https://", string.Empty)
.Replace("http://", string.Empty)); .Replace("http://", string.Empty));
intent.PutExtra("autofillFrameworkUri", parser.Uri); intent.PutExtra("autofillFrameworkUri", parser.Uri);

View File

@@ -12,7 +12,6 @@ namespace Bit.Droid.Autofill
private List<Field> _passwordFields = null; private List<Field> _passwordFields = null;
private List<Field> _usernameFields = null; private List<Field> _usernameFields = null;
private HashSet<string> _ignoreSearchTerms = new HashSet<string> { "search", "find", "recipient", "edit" }; private HashSet<string> _ignoreSearchTerms = new HashSet<string> { "search", "find", "recipient", "edit" };
private HashSet<string> _usernameTerms = new HashSet<string> { "email", "phone", "username" };
private HashSet<string> _passwordTerms = new HashSet<string> { "password", "pswd" }; private HashSet<string> _passwordTerms = new HashSet<string> { "password", "pswd" };
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>(); public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
@@ -54,14 +53,15 @@ namespace Bit.Droid.Autofill
if (HintToFieldsMap.ContainsKey(View.AutofillHintPassword)) if (HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
{ {
_passwordFields.AddRange(HintToFieldsMap[View.AutofillHintPassword]); _passwordFields.AddRange(HintToFieldsMap[View.AutofillHintPassword]);
return _passwordFields;
} }
} }
else
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
if (!_passwordFields.Any())
{ {
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList(); _passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
if (!_passwordFields.Any())
{
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
}
} }
return _passwordFields; return _passwordFields;
} }
@@ -86,26 +86,19 @@ namespace Bit.Droid.Autofill
{ {
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintUsername]); _usernameFields.AddRange(HintToFieldsMap[View.AutofillHintUsername]);
} }
if (_usernameFields.Any())
{
return _usernameFields;
}
} }
else
foreach (var passwordField in PasswordFields)
{ {
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId) foreach (var passwordField in PasswordFields)
.LastOrDefault();
if (usernameField != null)
{ {
_usernameFields.Add(usernameField); var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
.LastOrDefault();
if (usernameField != null)
{
_usernameFields.Add(usernameField);
}
} }
} }
if (!_usernameFields.Any())
{
_usernameFields = Fields.Where(f => FieldIsUsername(f)).ToList();
}
return _usernameFields; return _usernameFields;
} }
} }
@@ -328,23 +321,13 @@ namespace Bit.Droid.Autofill
} }
return inputTypePassword && !ValueContainsAnyTerms(f.IdEntry, _ignoreSearchTerms) && return inputTypePassword && !ValueContainsAnyTerms(f.IdEntry, _ignoreSearchTerms) &&
!ValueContainsAnyTerms(f.Hint, _ignoreSearchTerms) && !FieldIsUsername(f); !ValueContainsAnyTerms(f.Hint, _ignoreSearchTerms);
} }
private bool FieldHasPasswordTerms(Field f) private bool FieldHasPasswordTerms(Field f)
{ {
return ValueContainsAnyTerms(f.IdEntry, _passwordTerms) || ValueContainsAnyTerms(f.Hint, _passwordTerms); return ValueContainsAnyTerms(f.IdEntry, _passwordTerms) || ValueContainsAnyTerms(f.Hint, _passwordTerms);
} }
private bool FieldIsUsername(Field f)
{
return f.InputType.HasFlag(InputTypes.TextVariationWebEmailAddress) || FieldHasUsernameTerms(f);
}
private bool FieldHasUsernameTerms(Field f)
{
return ValueContainsAnyTerms(f.IdEntry, _usernameTerms) || ValueContainsAnyTerms(f.Hint, _usernameTerms);
}
private bool ValueContainsAnyTerms(string value, HashSet<string> terms) private bool ValueContainsAnyTerms(string value, HashSet<string> terms)
{ {
@@ -356,4 +339,4 @@ namespace Bit.Droid.Autofill
return terms.Any(t => lowerValue.Contains(t)); return terms.Any(t => lowerValue.Contains(t));
} }
} }
} }

View File

@@ -23,7 +23,6 @@ namespace Bit.Droid.Autofill
public FilledItem(CipherView cipher) public FilledItem(CipherView cipher)
{ {
Id = cipher.Id;
Name = cipher.Name; Name = cipher.Name;
Type = cipher.Type; Type = cipher.Type;
Subtitle = cipher.SubTitle; Subtitle = cipher.SubTitle;
@@ -56,7 +55,6 @@ namespace Bit.Droid.Autofill
} }
} }
public string Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Subtitle { get; set; } = string.Empty; public string Subtitle { get; set; } = string.Empty;
public int Icon { get; set; } = Resource.Drawable.login; public int Icon { get; set; } = Resource.Drawable.login;

View File

@@ -48,7 +48,7 @@ namespace Bit.Droid.Autofill
} }
else else
{ {
_uri = string.Concat(Core.Constants.AndroidAppProtocol, PackageName); _uri = string.Concat(Constants.AndroidAppProtocol, PackageName);
} }
return _uri; return _uri;
} }

View File

@@ -1,7 +0,0 @@
namespace Bit.Droid
{
public static class Constants
{
public const string PACKAGE_NAME = "com.x8bit.bitwarden";
}
}

View File

@@ -1,23 +0,0 @@
using Android.Widget;
using Bit.Droid.Effects;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportEffect(typeof(RemoveFontPaddingEffect), nameof(RemoveFontPaddingEffect))]
namespace Bit.Droid.Effects
{
public class RemoveFontPaddingEffect : PlatformEffect
{
protected override void OnAttached()
{
if (Control is TextView textView)
{
textView.SetIncludeFontPadding(false);
}
}
protected override void OnDetached()
{
}
}
}

View File

@@ -18,11 +18,8 @@ using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Droid.Autofill;
using Bit.Droid.Receivers; using Bit.Droid.Receivers;
using Bit.Droid.Utilities; using Bit.Droid.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xamarin.Essentials; using Xamarin.Essentials;
using ZXing.Net.Mobile.Android; using ZXing.Net.Mobile.Android;
using FileProvider = AndroidX.Core.Content.FileProvider; using FileProvider = AndroidX.Core.Content.FileProvider;
@@ -37,13 +34,11 @@ namespace Bit.Droid
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{ {
private IDeviceActionService _deviceActionService; private IDeviceActionService _deviceActionService;
private IFileService _fileService;
private IMessagingService _messagingService; private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService; private IBroadcasterService _broadcasterService;
private IStateService _stateService; private IStateService _stateService;
private IAppIdService _appIdService; private IAppIdService _appIdService;
private IEventService _eventService; private IEventService _eventService;
private IPushNotificationListenerService _pushNotificationListenerService;
private ILogger _logger; private ILogger _logger;
private PendingIntent _eventUploadPendingIntent; private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions; private AppOptions _appOptions;
@@ -61,13 +56,11 @@ namespace Bit.Droid
StrictMode.SetThreadPolicy(policy); StrictMode.SetThreadPolicy(policy);
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_fileService = ServiceContainer.Resolve<IFileService>();
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService"); _messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"); _broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
_stateService = ServiceContainer.Resolve<IStateService>("stateService"); _stateService = ServiceContainer.Resolve<IStateService>("stateService");
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService"); _appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
_eventService = ServiceContainer.Resolve<IEventService>("eventService"); _eventService = ServiceContainer.Resolve<IEventService>("eventService");
_pushNotificationListenerService = ServiceContainer.Resolve<IPushNotificationListenerService>();
_logger = ServiceContainer.Resolve<ILogger>("logger"); _logger = ServiceContainer.Resolve<ILogger>("logger");
TabLayoutResource = Resource.Layout.Tabbar; TabLayoutResource = Resource.Layout.Tabbar;
@@ -152,15 +145,6 @@ namespace Bit.Droid
AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this) AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this)
.GetAwaiter() .GetAwaiter()
.GetResult(); .GetResult();
if (Intent?.GetStringExtra(Core.Constants.NotificationData) is string notificationDataJson)
{
var notificationType = JToken.Parse(notificationDataJson).SelectToken(Core.Constants.NotificationDataType);
if (notificationType.ToString() == PasswordlessNotificationData.TYPE)
{
_pushNotificationListenerService.OnNotificationTapped(JsonConvert.DeserializeObject<PasswordlessNotificationData>(notificationDataJson)).FireAndForget();
}
}
} }
protected override void OnNewIntent(Intent intent) protected override void OnNewIntent(Intent intent)
@@ -170,7 +154,7 @@ namespace Bit.Droid
{ {
if (intent?.GetStringExtra("uri") is string uri) if (intent?.GetStringExtra("uri") is string uri)
{ {
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE); _messagingService.Send("popAllAndGoToAutofillCiphers");
if (_appOptions != null) if (_appOptions != null)
{ {
_appOptions.Uri = uri; _appOptions.Uri = uri;
@@ -178,7 +162,7 @@ namespace Bit.Droid
} }
else if (intent.GetBooleanExtra("generatorTile", false)) else if (intent.GetBooleanExtra("generatorTile", false))
{ {
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE); _messagingService.Send("popAllAndGoToTabGenerator");
if (_appOptions != null) if (_appOptions != null)
{ {
_appOptions.GeneratorTile = true; _appOptions.GeneratorTile = true;
@@ -186,7 +170,7 @@ namespace Bit.Droid
} }
else if (intent.GetBooleanExtra("myVaultTile", false)) else if (intent.GetBooleanExtra("myVaultTile", false))
{ {
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE); _messagingService.Send("popAllAndGoToTabMyVault");
if (_appOptions != null) if (_appOptions != null)
{ {
_appOptions.MyVaultTile = true; _appOptions.MyVaultTile = true;
@@ -198,7 +182,7 @@ namespace Bit.Droid
{ {
_appOptions.CreateSend = GetCreateSendRequest(intent); _appOptions.CreateSend = GetCreateSendRequest(intent);
} }
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE); _messagingService.Send("popAllAndGoToTabSend");
} }
else else
{ {
@@ -214,13 +198,13 @@ namespace Bit.Droid
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions, public async override void OnRequestPermissionsResult(int requestCode, string[] permissions,
[GeneratedEnum] Permission[] grantResults) [GeneratedEnum] Permission[] grantResults)
{ {
if (requestCode == Core.Constants.SelectFilePermissionRequestCode) if (requestCode == Constants.SelectFilePermissionRequestCode)
{ {
if (grantResults.Any(r => r != Permission.Granted)) if (grantResults.Any(r => r != Permission.Granted))
{ {
_messagingService.Send("selectFileCameraPermissionDenied"); _messagingService.Send("selectFileCameraPermissionDenied");
} }
await _fileService.SelectFileAsync(); await _deviceActionService.SelectFileAsync();
} }
else else
{ {
@@ -233,7 +217,7 @@ namespace Bit.Droid
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 (resultCode == Result.Ok &&
(requestCode == Core.Constants.SelectFileRequestCode || requestCode == Core.Constants.SaveFileRequestCode)) (requestCode == Constants.SelectFileRequestCode || requestCode == Constants.SaveFileRequestCode))
{ {
Android.Net.Uri uri = null; Android.Net.Uri uri = null;
string fileName = null; string fileName = null;
@@ -255,7 +239,7 @@ namespace Bit.Droid
return; return;
} }
if (requestCode == Core.Constants.SaveFileRequestCode) if (requestCode == Constants.SaveFileRequestCode)
{ {
_messagingService.Send("selectSaveFileResult", _messagingService.Send("selectSaveFileResult",
new Tuple<string, string>(uri.ToString(), fileName)); new Tuple<string, string>(uri.ToString(), fileName));
@@ -323,13 +307,13 @@ namespace Bit.Droid
{ {
var options = new AppOptions var options = new AppOptions
{ {
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra(AutofillConstants.AutofillFrameworkUri), Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false), MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false), GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
FromAutofillFramework = Intent.GetBooleanExtra(AutofillConstants.AutofillFramework, false), FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false),
CreateSend = GetCreateSendRequest(Intent) CreateSend = GetCreateSendRequest(Intent)
}; };
var fillType = Intent.GetIntExtra(AutofillConstants.AutofillFrameworkFillType, 0); var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
if (fillType > 0) if (fillType > 0)
{ {
options.FillType = (CipherType)fillType; options.FillType = (CipherType)fillType;
@@ -436,7 +420,7 @@ namespace Bit.Droid
return; return;
} }
var channel = new NotificationChannel(Core.Constants.AndroidNotificationChannelId, AppResources.AllNotifications, NotificationImportance.Default); var channel = new NotificationChannel(Constants.AndroidNotificationChannelId, AppResources.AllNotifications, NotificationImportance.Default);
if(GetSystemService(NotificationService) is NotificationManager notificationManager) if(GetSystemService(NotificationService) is NotificationManager notificationManager)
{ {
notificationManager.CreateNotificationChannel(channel); notificationManager.CreateNotificationChannel(channel);

View File

@@ -21,7 +21,6 @@ using Bit.App.Utilities;
using Bit.App.Pages; using Bit.App.Pages;
using Bit.App.Utilities.AccountManagement; using Bit.App.Utilities.AccountManagement;
using Bit.App.Controls; using Bit.App.Controls;
using Bit.Core.Enums;
#if !FDROID #if !FDROID
using Android.Gms.Security; using Android.Gms.Security;
#endif #endif
@@ -46,16 +45,9 @@ namespace Bit.Droid
if (ServiceContainer.RegisteredServices.Count == 0) if (ServiceContainer.RegisteredServices.Count == 0)
{ {
RegisterLocalServices(); RegisterLocalServices();
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Core.Constants.ClearCiphersCacheKey, ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
Core.Constants.AndroidAllClearCipherCacheKeys); Constants.AndroidAllClearCipherCacheKeys);
ServiceContainer.Register<IWatchDeviceService>(new WatchDeviceService(ServiceContainer.Resolve<ICipherService>(),
ServiceContainer.Resolve<IEnvironmentService>(),
ServiceContainer.Resolve<IStateService>(),
ServiceContainer.Resolve<IVaultTimeoutService>()));
InitializeAppSetup(); InitializeAppSetup();
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged // TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
@@ -81,10 +73,8 @@ namespace Bit.Droid
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"), ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
ServiceContainer.Resolve<IAuthService>("authService"), ServiceContainer.Resolve<IAuthService>("authService"),
ServiceContainer.Resolve<ILogger>("logger"), ServiceContainer.Resolve<ILogger>("logger"),
ServiceContainer.Resolve<IMessagingService>("messagingService"), ServiceContainer.Resolve<IMessagingService>("messagingService"));
ServiceContainer.Resolve<IWatchDeviceService>(), ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
ServiceContainer.Resolve<IConditionedAwaiterManager>());
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
} }
#if !FDROID #if !FDROID
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat) if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
@@ -145,23 +135,19 @@ namespace Bit.Droid
var secureStorageService = new SecureStorageService(); var secureStorageService = new SecureStorageService();
var cryptoPrimitiveService = new CryptoPrimitiveService(); var cryptoPrimitiveService = new CryptoPrimitiveService();
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage); var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
var storageMediatorService = new StorageMediatorService(mobileStorageService, secureStorageService, preferencesStorage); var stateService = new StateService(mobileStorageService, secureStorageService, messagingService);
var stateService = new StateService(mobileStorageService, secureStorageService, storageMediatorService, messagingService);
var stateMigrationService = var stateMigrationService =
new StateMigrationService(DeviceType.Android, liteDbStorage, preferencesStorage, secureStorageService); new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
var clipboardService = new ClipboardService(stateService); var clipboardService = new ClipboardService(stateService);
var deviceActionService = new DeviceActionService(stateService, messagingService); var deviceActionService = new DeviceActionService(clipboardService, stateService, messagingService,
var fileService = new FileService(stateService, broadcasterService); broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService, var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
messagingService, broadcasterService); messagingService, broadcasterService);
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService, var biometricService = new BiometricService();
platformUtilsService, new LazyResolve<IEventService>());
var biometricService = new BiometricService(stateService);
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService); var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService); var cryptoService = new CryptoService(stateService, cryptoFunctionService);
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService); var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService); ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
ServiceContainer.Register<IMessagingService>("messagingService", messagingService); ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
ServiceContainer.Register<ILocalizeService>("localizeService", localizeService); ServiceContainer.Register<ILocalizeService>("localizeService", localizeService);
@@ -169,13 +155,10 @@ namespace Bit.Droid
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService); ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService); ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService); ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IStorageMediatorService>(storageMediatorService);
ServiceContainer.Register<IStateService>("stateService", stateService); ServiceContainer.Register<IStateService>("stateService", stateService);
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService); ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService); ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService);
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService); ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IFileService>(fileService);
ServiceContainer.Register<IAutofillHandler>(autofillHandler);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService); ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService); ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService); ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
@@ -202,9 +185,7 @@ namespace Bit.Droid
private void Bootstrap() private void Bootstrap()
{ {
var locale = ServiceContainer.Resolve<IStateService>().GetLocale(); (ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService).Init();
(ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService)
.Init(locale != null ? new System.Globalization.CultureInfo(locale) : null);
ServiceContainer.Resolve<IAuthService>("authService").Init(); ServiceContainer.Resolve<IAuthService>("authService").Init();
// Note: This is not awaited // Note: This is not awaited
var bootstrapTask = BootstrapAsync(); var bootstrapTask = BootstrapAsync();

View File

@@ -1,6 +1,6 @@
<?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="2023.7.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionCode="1" android:versionName="2022.10.0" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" /> <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="32" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -40,16 +40,10 @@
</intent-filter> </intent-filter>
</activity> </activity>
</application> </application>
<!-- Support for Xamarin.Essentials.Browser.OpenAsync (for Android > 11) --> <!-- Package visibility (for Android 11+) -->
<!-- Related docs: https://learn.microsoft.com/en-us/xamarin/essentials/open-browser?tabs=android -->
<queries> <queries>
<intent> <intent>
<action android:name="android.intent.action.VIEW" /> <action android:name="*" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent> </intent>
</queries> </queries>
</manifest> </manifest>

View File

@@ -1,5 +1,4 @@
using Android.Content; using Android.Content;
using Android.OS;
namespace Bit.Droid.Receivers namespace Bit.Droid.Receivers
{ {
@@ -9,17 +8,7 @@ namespace Bit.Droid.Receivers
public override void OnReceive(Context context, Intent intent) public override void OnReceive(Context context, Intent intent)
{ {
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager; var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
if (clipboardManager == null) clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
{
return;
}
// ClearPrimaryClip is supported down to API 28 with mixed results, so we're requiring 33+ instead
if ((int)Build.VERSION.SdkInt < 33)
{
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
return;
}
clipboardManager.ClearPrimaryClip();
} }
} }
} }

View File

@@ -1,41 +0,0 @@
using Android.Content;
using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.App.Services;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using CoreConstants = Bit.Core.Constants;
namespace Bit.Droid.Receivers
{
[BroadcastReceiver(Name = Constants.PACKAGE_NAME + "." + nameof(NotificationDismissReceiver), Exported = false)]
public class NotificationDismissReceiver : BroadcastReceiver
{
private readonly LazyResolve<IPushNotificationListenerService> _pushNotificationListenerService = new LazyResolve<IPushNotificationListenerService>();
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
public override void OnReceive(Context context, Intent intent)
{
try
{
if (intent?.GetStringExtra(CoreConstants.NotificationData) is string notificationDataJson)
{
var notificationType = JToken.Parse(notificationDataJson).SelectToken(CoreConstants.NotificationDataType);
if (notificationType.ToString() == PasswordlessNotificationData.TYPE)
{
_pushNotificationListenerService.Value.OnNotificationDismissed(JsonConvert.DeserializeObject<PasswordlessNotificationData>(notificationDataJson)).FireAndForget();
}
}
}
catch (System.Exception ex)
{
_logger.Value.Exception(ex);
}
}
}
}

View File

@@ -1,43 +0,0 @@
using System.ComponentModel;
using Android.Content;
using Android.OS;
using Bit.App.Controls;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
namespace Bit.Droid.Renderers
{
public class CustomLabelRenderer : LabelRenderer
{
public CustomLabelRenderer(Context context)
: base(context)
{ }
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
{
base.OnElementChanged(e);
if (Control != null && e.NewElement is CustomLabel label)
{
if (label.FontWeight.HasValue && Build.VERSION.SdkInt >= BuildVersionCodes.P)
{
Control.Typeface = Android.Graphics.Typeface.Create(null, label.FontWeight.Value, false);
}
}
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
var label = sender as CustomLabel;
switch (e.PropertyName)
{
case nameof(CustomLabel.AutomationId):
Control.ContentDescription = label.AutomationId;
break;
}
base.OnElementPropertyChanged(sender, e);
}
}
}

View File

@@ -44,7 +44,6 @@ namespace Bit.Droid.Renderers
} }
if (Control != null) if (Control != null)
{ {
Control.SetHintTextColor(ThemeHelpers.MutedColor);
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null); var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null);
if (t is GradientDrawable thumb) if (t is GradientDrawable thumb)
{ {

View File

@@ -1,35 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="129"
android:viewportHeight="124"
android:width="129dp"
android:height="124dp">
<path
android:pathData="M126.8227 61.9441A59.6843 59.6843 0 0 1 7.4541 61.9441A59.6843 59.6843 0 0 1 126.8227 61.9441Z"
android:fillColor="#F0F0F0"
android:strokeColor="#89929F"
android:strokeWidth="3" />
<path
android:pathData="M21.6167 100.851C52.597 103.31 79.6937 80.3264 82.1391 49.5156C83.6205 30.8497 76.0789 14.8844 62.7275 3.63385"
android:strokeColor="#89929F"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M14.5633 34.2845C12.2035 66.7711 38.5225 96.3429 72.6666 98.8232C74.2596 98.9389 78.629 98.9975 80.1951 99C84.6245 98.8232 97.8063 96.593 106.813 91.8485C113.439 88.3581 119.745 84.6984 124.644 79.1121"
android:strokeColor="#89929F"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M124.502 48.5051C106.554 24.3817 68.8237 21.6709 41.4178 42.0617C24.8146 54.4149 14.7327 72.4183 13.9255 90.1427"
android:strokeColor="#89929F"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M83.4034 28.3934A5 5 0 0 1 73.4034 28.3934A5 5 0 0 1 83.4034 28.3934Z"
android:fillColor="#89929F" />
<path
android:pathData="M24.7698 66.5518A5 5 0 0 1 14.7698 66.5518A5 5 0 0 1 24.7698 66.5518Z"
android:fillColor="#89929F" />
<path
android:pathData="M57.344 94.4726A5 5 0 0 1 47.344 94.4726A5 5 0 0 1 57.344 94.4726Z"
android:fillColor="#89929F" />
</vector>

View File

@@ -1,35 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="129"
android:viewportHeight="124"
android:width="129dp"
android:height="124dp">
<path
android:pathData="M126.8227 61.9441A59.6843 59.6843 0 0 1 7.4541 61.9441A59.6843 59.6843 0 0 1 126.8227 61.9441Z"
android:fillColor="@android:color/transparent"
android:strokeColor="#A3A3A3"
android:strokeWidth="3" />
<path
android:pathData="M21.6167 100.851C52.597 103.31 79.6937 80.3264 82.1391 49.5156C83.6205 30.8497 76.0789 14.8844 62.7275 3.63385"
android:strokeColor="#A3A3A3"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M14.5633 34.2845C12.2035 66.7711 38.5225 96.3429 72.6666 98.8232C74.2596 98.9389 78.629 98.9975 80.1951 99C84.6245 98.8232 97.8063 96.593 106.813 91.8485C113.439 88.3581 119.745 84.6984 124.644 79.1121"
android:strokeColor="#A3A3A3"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M124.502 48.5051C106.554 24.3817 68.8237 21.6709 41.4178 42.0617C24.8146 54.4149 14.7327 72.4183 13.9255 90.1427"
android:strokeColor="#A3A3A3"
android:strokeWidth="1.5"
android:strokeLineCap="round" />
<path
android:pathData="M83.4034 28.3934A5 5 0 0 1 73.4034 28.3934A5 5 0 0 1 83.4034 28.3934Z"
android:fillColor="#A3A3A3" />
<path
android:pathData="M24.7698 66.5518A5 5 0 0 1 14.7698 66.5518A5 5 0 0 1 24.7698 66.5518Z"
android:fillColor="#A3A3A3" />
<path
android:pathData="M57.344 94.4726A5 5 0 0 1 47.344 94.4726A5 5 0 0 1 57.344 94.4726Z"
android:fillColor="#A3A3A3" />
</vector>

View File

@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="30dp"
android:paddingRight="30dp">
<TextView
android:id="@+id/lblHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/dialog_header_text_size"
android:layout_marginTop="5dp"
android:layout_marginBottom="-3dp"
android:labelFor="@+id/txtValue"/>
<EditText
android:id="@id/txtValue"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/dialog_input_text_size"/>
<TextView
android:id="@+id/lblValueSubinfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="@dimen/dialog_sub_value_info_text_size"/>
</LinearLayout>

View File

@@ -2,7 +2,4 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<dimen name="design_bottom_navigation_text_size" tools:override="true">15sp</dimen> <dimen name="design_bottom_navigation_text_size" tools:override="true">15sp</dimen>
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">15sp</dimen> <dimen name="design_bottom_navigation_active_text_size" tools:override="true">15sp</dimen>
<dimen name="dialog_input_text_size">16sp</dimen>
<dimen name="dialog_header_text_size">12sp</dimen>
<dimen name="dialog_sub_value_info_text_size">12sp</dimen>
</resources> </resources>

View File

@@ -17,9 +17,6 @@
<compatibility-package <compatibility-package
android:name="alook.browser.google" android:name="alook.browser.google"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="app.vanadium.browser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.amazon.cloud9" android:name="com.amazon.cloud9"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -92,9 +89,6 @@
<compatibility-package <compatibility-package
android:name="com.kiwibrowser.browser.dev" android:name="com.kiwibrowser.browser.dev"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.lemurbrowser.exts"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.microsoft.emmx" android:name="com.microsoft.emmx"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -119,9 +113,6 @@
<compatibility-package <compatibility-package
android:name="com.naver.whale" android:name="com.naver.whale"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.neeva.app"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.opera.browser" android:name="com.opera.browser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -146,9 +137,6 @@
<compatibility-package <compatibility-package
android:name="com.qwant.liberty" android:name="com.qwant.liberty"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.rainsee.create"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.sec.android.app.sbrowser" android:name="com.sec.android.app.sbrowser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -173,15 +161,6 @@
<compatibility-package <compatibility-package
android:name="com.yandex.browser" android:name="com.yandex.browser"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.yjllq.internet"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.yjllq.kito"
android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="com.yujian.ResideMenuDemo"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="com.z28j.feel" android:name="com.z28j.feel"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
@@ -203,9 +182,6 @@
<compatibility-package <compatibility-package
android:name="mark.via.gp" android:name="mark.via.gp"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>
<compatibility-package
android:name="net.dezor.browser"
android:maxLongVersionCode="10000000000"/>
<compatibility-package <compatibility-package
android:name="net.slions.fulguris.full.download" android:name="net.slions.fulguris.full.download"
android:maxLongVersionCode="10000000000"/> android:maxLongVersionCode="10000000000"/>

View File

@@ -1,21 +1,15 @@
#if !FDROID #if !FDROID
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using AndroidX.Core.App; using AndroidX.Core.App;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models;
using Bit.Core; using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Droid.Receivers;
using Bit.Droid.Utilities; using Bit.Droid.Utilities;
using Newtonsoft.Json;
using Xamarin.Forms; using Xamarin.Forms;
using static Xamarin.Essentials.Platform;
using Intent = Android.Content.Intent;
namespace Bit.Droid.Services namespace Bit.Droid.Services
{ {
@@ -73,39 +67,28 @@ namespace Bit.Droid.Services
} }
} }
public void SendLocalNotification(string title, string message, BaseNotificationData data) public void SendLocalNotification(string title, string message, string notificationId)
{ {
if (string.IsNullOrEmpty(data.Id)) if (string.IsNullOrEmpty(notificationId))
{ {
throw new ArgumentNullException("notificationId cannot be null or empty."); throw new ArgumentNullException("notificationId cannot be null or empty.");
} }
var context = Android.App.Application.Context; var context = Android.App.Application.Context;
var intent = new Intent(context, typeof(MainActivity)); var intent = new Intent(context, typeof(MainActivity));
intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true); var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true);
var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags); var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags);
var builder = new NotificationCompat.Builder(context, Constants.AndroidNotificationChannelId)
var deleteIntent = new Intent(context, typeof(NotificationDismissReceiver));
deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags);
var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId)
.SetContentIntent(pendingIntent) .SetContentIntent(pendingIntent)
.SetContentTitle(title) .SetContentTitle(title)
.SetContentText(message) .SetContentText(message)
.SetTimeoutAfter(Constants.PasswordlessNotificationTimeoutInMinutes * 60000)
.SetSmallIcon(Resource.Drawable.ic_notification) .SetSmallIcon(Resource.Drawable.ic_notification)
.SetColor((int)Android.Graphics.Color.White) .SetColor((int)Android.Graphics.Color.White)
.SetDeleteIntent(deletePendingIntent)
.SetAutoCancel(true); .SetAutoCancel(true);
if (data is PasswordlessNotificationData passwordlessNotificationData && passwordlessNotificationData.TimeoutInMinutes > 0)
{
builder.SetTimeoutAfter(passwordlessNotificationData.TimeoutInMinutes * 60000);
}
var notificationManager = NotificationManagerCompat.From(context); var notificationManager = NotificationManagerCompat.From(context);
notificationManager.Notify(int.Parse(data.Id), builder.Build()); notificationManager.Notify(int.Parse(notificationId), builder.Build());
} }
} }
} }

View File

@@ -1,215 +0,0 @@
using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.App.Assist;
using Android.Content;
using Android.OS;
using Android.Provider;
using Android.Views.Autofill;
using Bit.App.Resources;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Bit.Droid.Autofill;
using Plugin.CurrentActivity;
namespace Bit.Droid.Services
{
public class AutofillHandler : IAutofillHandler
{
private readonly IStateService _stateService;
private readonly IMessagingService _messagingService;
private readonly IClipboardService _clipboardService;
private readonly IPlatformUtilsService _platformUtilsService;
private readonly LazyResolve<IEventService> _eventService;
public AutofillHandler(IStateService stateService,
IMessagingService messagingService,
IClipboardService clipboardService,
IPlatformUtilsService platformUtilsService,
LazyResolve<IEventService> eventService)
{
_stateService = stateService;
_messagingService = messagingService;
_clipboardService = clipboardService;
_platformUtilsService = platformUtilsService;
_eventService = eventService;
}
public bool AutofillServiceEnabled()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return false;
}
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var afm = (AutofillManager)activity.GetSystemService(
Java.Lang.Class.FromType(typeof(AutofillManager)));
return afm.IsEnabled && afm.HasEnabledAutofillServices;
}
catch
{
return false;
}
}
public bool SupportsAutofillService()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return false;
}
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
var manager = activity.GetSystemService(type) as AutofillManager;
return manager.IsAutofillSupported;
}
catch
{
return false;
}
}
public void Autofill(CipherView cipher)
{
var activity = CrossCurrentActivity.Current.Activity as Xamarin.Forms.Platform.Android.FormsAppCompatActivity;
if (activity == null)
{
return;
}
if (activity.Intent?.GetBooleanExtra(AutofillConstants.AutofillFramework, false) ?? false)
{
if (cipher == null)
{
activity.SetResult(Result.Canceled);
activity.Finish();
return;
}
var structure = activity.Intent.GetParcelableExtra(
AutofillManager.ExtraAssistStructure) as AssistStructure;
if (structure == null)
{
activity.SetResult(Result.Canceled);
activity.Finish();
return;
}
var parser = new Parser(structure, activity.ApplicationContext);
parser.Parse();
if ((!parser.FieldCollection?.Fields?.Any() ?? true) || string.IsNullOrWhiteSpace(parser.Uri))
{
activity.SetResult(Result.Canceled);
activity.Finish();
return;
}
var task = CopyTotpAsync(cipher);
var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher), false);
var replyIntent = new Intent();
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
activity.SetResult(Result.Ok, replyIntent);
activity.Finish();
var eventTask = _eventService.Value.CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
}
else
{
var data = new Intent();
if (cipher?.Login == null)
{
data.PutExtra("canceled", "true");
}
else
{
var task = CopyTotpAsync(cipher);
data.PutExtra("uri", cipher.Login.Uri);
data.PutExtra("username", cipher.Login.Username);
data.PutExtra("password", cipher.Login.Password);
}
if (activity.Parent == null)
{
activity.SetResult(Result.Ok, data);
}
else
{
activity.Parent.SetResult(Result.Ok, data);
}
activity.Finish();
_messagingService.Send("finishMainActivity");
if (cipher != null)
{
var eventTask = _eventService.Value.CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
}
}
}
public void CloseAutofill()
{
Autofill(null);
}
public bool AutofillAccessibilityServiceRunning()
{
var enabledServices = Settings.Secure.GetString(Application.Context.ContentResolver,
Settings.Secure.EnabledAccessibilityServices);
return Application.Context.PackageName != null &&
(enabledServices?.Contains(Application.Context.PackageName) ?? false);
}
public bool AutofillAccessibilityOverlayPermitted()
{
return Accessibility.AccessibilityHelpers.OverlayPermitted();
}
public void DisableAutofillService()
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
var manager = activity.GetSystemService(type) as AutofillManager;
manager.DisableAutofillServices();
}
catch { }
}
public bool AutofillServicesEnabled()
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
{
// Android 5-6: Both accessibility & overlay are required or nothing happens
return AutofillAccessibilityServiceRunning() && AutofillAccessibilityOverlayPermitted();
}
if (Build.VERSION.SdkInt == BuildVersionCodes.N)
{
// Android 7: Only accessibility is required (overlay is optional when using quick-action tile)
return AutofillAccessibilityServiceRunning();
}
// Android 8+: Either autofill or accessibility is required
return AutofillServiceEnabled() || AutofillAccessibilityServiceRunning();
}
private async Task CopyTotpAsync(CipherView cipher)
{
if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp))
{
var autoCopyDisabled = await _stateService.GetDisableAutoTotpCopyAsync();
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault())
{
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
if (totp != null)
{
await _clipboardService.CopyTextAsync(totp);
_platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
}
}
}
}
}
}

View File

@@ -4,6 +4,7 @@ using Android.OS;
using Android.Security.Keystore; using Android.Security.Keystore;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities;
using Java.Security; using Java.Security;
using Javax.Crypto; using Javax.Crypto;
@@ -11,8 +12,6 @@ namespace Bit.Droid.Services
{ {
public class BiometricService : IBiometricService public class BiometricService : IBiometricService
{ {
private readonly IStateService _stateService;
private const string KeyName = "com.8bit.bitwarden.biometric_integrity"; private const string KeyName = "com.8bit.bitwarden.biometric_integrity";
private const string KeyStoreName = "AndroidKeyStore"; private const string KeyStoreName = "AndroidKeyStore";
@@ -24,28 +23,28 @@ namespace Bit.Droid.Services
private readonly KeyStore _keystore; private readonly KeyStore _keystore;
public BiometricService(IStateService stateService) public BiometricService()
{ {
_stateService = stateService;
_keystore = KeyStore.GetInstance(KeyStoreName); _keystore = KeyStore.GetInstance(KeyStoreName);
_keystore.Load(null); _keystore.Load(null);
} }
public async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null) public Task<bool> SetupBiometricAsync(string bioIntegrityKey = null)
{ {
// bioIntegrityKey used in iOS only
if (Build.VERSION.SdkInt >= BuildVersionCodes.M) if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{ {
await CreateKeyAsync(bioIntegritySrcKey); CreateKey();
} }
return true; return Task.FromResult(true);
} }
public async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null) public Task<bool> ValidateIntegrityAsync(string bioIntegrityKey = null)
{ {
if (Build.VERSION.SdkInt < BuildVersionCodes.M) if (Build.VERSION.SdkInt < BuildVersionCodes.M)
{ {
return true; return Task.FromResult(true);
} }
try try
@@ -56,7 +55,7 @@ namespace Bit.Droid.Services
if (key == null || cipher == null) if (key == null || cipher == null)
{ {
return true; return Task.FromResult(true);
} }
cipher.Init(CipherMode.EncryptMode, key); cipher.Init(CipherMode.EncryptMode, key);
@@ -64,32 +63,25 @@ namespace Bit.Droid.Services
catch (KeyPermanentlyInvalidatedException e) catch (KeyPermanentlyInvalidatedException e)
{ {
// Biometric has changed // Biometric has changed
await ClearStateAsync(bioIntegritySrcKey); return Task.FromResult(false);
return false;
} }
catch (UnrecoverableKeyException e) catch (UnrecoverableKeyException e)
{ {
// Biometric was disabled and re-enabled // Biometric was disabled and re-enabled
await ClearStateAsync(bioIntegritySrcKey); return Task.FromResult(false);
return false;
} }
catch (InvalidKeyException e) catch (InvalidKeyException e)
{ {
// Fallback for old bitwarden users without a key // Fallback for old bitwarden users without a key
LoggerHelper.LogEvenIfCantBeResolved(e); LoggerHelper.LogEvenIfCantBeResolved(e);
await CreateKeyAsync(bioIntegritySrcKey); CreateKey();
} }
return true; return Task.FromResult(true);
} }
private async Task CreateKeyAsync(string bioIntegritySrcKey = null) private void CreateKey()
{ {
bioIntegritySrcKey ??= Core.Constants.BiometricIntegritySourceKey;
await _stateService.SetSystemBiometricIntegrityState(bioIntegritySrcKey,
await GetStateAsync(bioIntegritySrcKey));
await _stateService.SetAccountBiometricIntegrityValidAsync(bioIntegritySrcKey);
try try
{ {
var keyGen = KeyGenerator.GetInstance(KeyAlgorithm, KeyStoreName); var keyGen = KeyGenerator.GetInstance(KeyAlgorithm, KeyStoreName);
@@ -109,16 +101,5 @@ namespace Bit.Droid.Services
LoggerHelper.LogEvenIfCantBeResolved(e); LoggerHelper.LogEvenIfCantBeResolved(e);
} }
} }
private async Task<string> GetStateAsync(string bioIntegritySrcKey)
{
return await _stateService.GetSystemBiometricIntegrityState(bioIntegritySrcKey) ??
Guid.NewGuid().ToString();
}
private async Task ClearStateAsync(string bioIntegritySrcKey)
{
await _stateService.SetSystemBiometricIntegrityState(bioIntegritySrcKey, null);
}
} }
} }

View File

@@ -6,6 +6,7 @@ using Android.OS;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Droid.Receivers; using Bit.Droid.Receivers;
using Bit.Droid.Utilities; using Bit.Droid.Utilities;
using Plugin.CurrentActivity;
using Xamarin.Essentials; using Xamarin.Essentials;
namespace Bit.Droid.Services namespace Bit.Droid.Services
@@ -20,9 +21,9 @@ namespace Bit.Droid.Services
_stateService = stateService; _stateService = stateService;
_clearClipboardPendingIntent = new Lazy<PendingIntent>(() => _clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
PendingIntent.GetBroadcast(Application.Context, PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity,
0, 0,
new Intent(Application.Context, typeof(ClearClipboardAlarmReceiver)), new Intent(CrossCurrentActivity.Current.Activity, typeof(ClearClipboardAlarmReceiver)),
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false))); AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false)));
} }
@@ -44,14 +45,22 @@ namespace Bit.Droid.Services
} }
catch (Java.Lang.SecurityException ex) when (ex.Message.Contains("does not belong to")) catch (Java.Lang.SecurityException ex) when (ex.Message.Contains("does not belong to"))
{ {
// #1962 Just ignore, the content is copied either way but there is some app interfering in the process // #1962 Just ignore, the content is copied either way but there is some app interfiering in the process
// that the OS catches and just throws this exception. // that the OS catches and just throws this exception.
} }
} }
public bool IsCopyNotificationHandledByPlatform()
{
// Android 13+ provides built-in notification when text is copied to the clipboard
return (int)Build.VERSION.SdkInt >= 33;
}
private void CopyToClipboard(string text, bool isSensitive = true) private void CopyToClipboard(string text, bool isSensitive = true)
{ {
var clipboardManager = Application.Context.GetSystemService(Context.ClipboardService) as ClipboardManager; var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var clipboardManager = activity.GetSystemService(
Context.ClipboardService) as Android.Content.ClipboardManager;
var clipData = ClipData.NewPlainText("bitwarden", text); var clipData = ClipData.NewPlainText("bitwarden", text);
if (isSensitive) if (isSensitive)
{ {
@@ -78,7 +87,7 @@ namespace Bit.Droid.Services
return; return;
} }
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs; var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs;
var alarmManager = Application.Context.GetSystemService(Context.AlarmService) as AlarmManager; var alarmManager = CrossCurrentActivity.Current.Activity.GetSystemService(Context.AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value); alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value);
} }
} }

View File

@@ -5,8 +5,6 @@ using Org.BouncyCastle.Crypto.Digests;
using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Crypto.Parameters;
using System; using System;
using System.Runtime.InteropServices;
using Java.Lang;
namespace Bit.Droid.Services namespace Bit.Droid.Services
{ {
@@ -35,19 +33,5 @@ namespace Bit.Droid.Services
generator.Init(password, salt, iterations); generator.Init(password, salt, iterations);
return ((KeyParameter)generator.GenerateDerivedMacParameters(keySize)).GetKey(); return ((KeyParameter)generator.GenerateDerivedMacParameters(keySize)).GetKey();
} }
public byte[] Argon2id(byte[] password, byte[] salt, int iterations, int memory, int parallelism)
{
JavaSystem.LoadLibrary("argon2");
int keySize = 32;
var key = new byte[keySize];
argon2id_hash_raw(iterations, memory, parallelism,
password, password.Length, salt, salt.Length, key, key.Length);
return key;
}
[DllImport("argon2", EntryPoint = "argon2id_hash_raw")]
private static extern int argon2id_hash_raw(int timeCost, int memoryCost, int parallelism,
byte[] pwd, int pwdlen, byte[] salt, int saltlen, byte[] hash, int hashlen);
} }
} }

View File

@@ -1,6 +1,11 @@
using System; using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Android;
using Android.App; using Android.App;
using Android.App.Assist;
using Android.Content; using Android.Content;
using Android.Content.PM; using Android.Content.PM;
using Android.Nfc; using Android.Nfc;
@@ -9,36 +14,59 @@ using Android.Provider;
using Android.Text; using Android.Text;
using Android.Text.Method; using Android.Text.Method;
using Android.Views; using Android.Views;
using Android.Views.Autofill;
using Android.Views.InputMethods; using Android.Views.InputMethods;
using Android.Webkit;
using Android.Widget; using Android.Widget;
using AndroidX.Core.App;
using AndroidX.Core.Content;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Resources; using Bit.App.Resources;
using Bit.App.Utilities; using Bit.Core;
using Bit.App.Utilities.Prompts;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.View;
using Bit.Core.Utilities;
using Bit.Droid.Autofill;
using Bit.Droid.Utilities; using Bit.Droid.Utilities;
using Plugin.CurrentActivity; using Plugin.CurrentActivity;
using Xamarin.Forms.Platform.Android;
namespace Bit.Droid.Services namespace Bit.Droid.Services
{ {
public class DeviceActionService : IDeviceActionService public class DeviceActionService : IDeviceActionService
{ {
private readonly IClipboardService _clipboardService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IBroadcasterService _broadcasterService;
private readonly Func<IEventService> _eventServiceFunc;
private AlertDialog _progressDialog; private AlertDialog _progressDialog;
object _progressDialogLock = new object(); object _progressDialogLock = new object();
private bool _cameraPermissionsDenied;
private Toast _toast; private Toast _toast;
private string _userAgent; private string _userAgent;
public DeviceActionService( public DeviceActionService(
IClipboardService clipboardService,
IStateService stateService, IStateService stateService,
IMessagingService messagingService) IMessagingService messagingService,
IBroadcasterService broadcasterService,
Func<IEventService> eventServiceFunc)
{ {
_clipboardService = clipboardService;
_stateService = stateService; _stateService = stateService;
_messagingService = messagingService; _messagingService = messagingService;
_broadcasterService = broadcasterService;
_eventServiceFunc = eventServiceFunc;
_broadcasterService.Subscribe(nameof(DeviceActionService), (message) =>
{
if (message.Command == "selectFileCameraPermissionDenied")
{
_cameraPermissionsDenied = true;
}
});
} }
public string DeviceUserAgent public string DeviceUserAgent
@@ -71,17 +99,14 @@ namespace Bit.Droid.Services
public bool LaunchApp(string appName) public bool LaunchApp(string appName)
{ {
if ((int)Build.VERSION.SdkInt < 33)
{
// API 33 required to avoid using wildcard app visibility or dangerous permissions
// https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
return false;
}
var activity = CrossCurrentActivity.Current.Activity; var activity = CrossCurrentActivity.Current.Activity;
appName = appName.Replace("androidapp://", string.Empty); appName = appName.Replace("androidapp://", string.Empty);
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName); var launchIntent = activity.PackageManager.GetLaunchIntentForPackage(appName);
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null); if (launchIntent != null)
return launchIntentSender != null; {
activity.StartActivity(launchIntent);
}
return launchIntent != null;
} }
public async Task ShowLoadingAsync(string text) public async Task ShowLoadingAsync(string text)
@@ -187,6 +212,184 @@ namespace Bit.Droid.Services
return true; return true;
} }
public bool OpenFile(byte[] fileData, string id, string fileName)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = BuildOpenFileIntent(fileData, fileName);
if (intent == null)
{
return false;
}
activity.StartActivity(intent);
return true;
}
catch { }
return false;
}
public bool CanOpenFile(string fileName)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = BuildOpenFileIntent(new byte[0], string.Concat("opentest_", fileName));
if (intent == null)
{
return false;
}
var activities = activity.PackageManager.QueryIntentActivities(intent,
PackageInfoFlags.MatchDefaultOnly);
return (activities?.Count ?? 0) > 0;
}
catch { }
return false;
}
private Intent BuildOpenFileIntent(byte[] fileData, string fileName)
{
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if (extension == null)
{
return null;
}
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if (mimeType == null)
{
return null;
}
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var cachePath = activity.CacheDir;
var filePath = Path.Combine(cachePath.Path, fileName);
File.WriteAllBytes(filePath, fileData);
var file = new Java.IO.File(cachePath, fileName);
if (!file.IsFile)
{
return null;
}
try
{
var intent = new Intent(Intent.ActionView);
var uri = FileProvider.GetUriForFile(activity.ApplicationContext,
"com.x8bit.bitwarden.fileprovider", file);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
return intent;
}
catch { }
return null;
}
public bool SaveFile(byte[] fileData, string id, string fileName, string contentUri)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (contentUri != null)
{
var uri = Android.Net.Uri.Parse(contentUri);
var stream = activity.ContentResolver.OpenOutputStream(uri);
// Using java bufferedOutputStream due to this issue:
// https://github.com/xamarin/xamarin-android/issues/3498
var javaStream = new Java.IO.BufferedOutputStream(stream);
javaStream.Write(fileData);
javaStream.Flush();
javaStream.Close();
return true;
}
// Prompt for location to save file
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if (extension == null)
{
return false;
}
string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if (mimeType == null)
{
// Unable to identify so fall back to generic "any" type
mimeType = "*/*";
}
var intent = new Intent(Intent.ActionCreateDocument);
intent.SetType(mimeType);
intent.AddCategory(Intent.CategoryOpenable);
intent.PutExtra(Intent.ExtraTitle, fileName);
activity.StartActivityForResult(intent, Constants.SaveFileRequestCode);
return true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace);
}
return false;
}
public async Task ClearCacheAsync()
{
try
{
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
}
catch (Exception) { }
}
public Task SelectFileAsync()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var hasStorageWritePermission = !_cameraPermissionsDenied &&
HasPermission(Manifest.Permission.WriteExternalStorage);
var additionalIntents = new List<IParcelable>();
if (activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera))
{
var hasCameraPermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.Camera);
if (!_cameraPermissionsDenied && !hasStorageWritePermission)
{
AskPermission(Manifest.Permission.WriteExternalStorage);
return Task.FromResult(0);
}
if (!_cameraPermissionsDenied && !hasCameraPermission)
{
AskPermission(Manifest.Permission.Camera);
return Task.FromResult(0);
}
if (!_cameraPermissionsDenied && hasCameraPermission && hasStorageWritePermission)
{
try
{
var file = new Java.IO.File(activity.FilesDir, "temp_camera_photo.jpg");
if (!file.Exists())
{
file.ParentFile.Mkdirs();
file.CreateNewFile();
}
var outputFileUri = FileProvider.GetUriForFile(activity,
"com.x8bit.bitwarden.fileprovider", file);
additionalIntents.AddRange(GetCameraIntents(outputFileUri));
}
catch (Java.IO.IOException) { }
}
}
var docIntent = new Intent(Intent.ActionOpenDocument);
docIntent.AddCategory(Intent.CategoryOpenable);
docIntent.SetType("*/*");
var chooserIntent = Intent.CreateChooser(docIntent, AppResources.FileSource);
if (additionalIntents.Count > 0)
{
chooserIntent.PutExtra(Intent.ExtraInitialIntents, additionalIntents.ToArray());
}
activity.StartActivityForResult(chooserIntent, Constants.SelectFileRequestCode);
return Task.FromResult(0);
}
public Task<string> DisplayPromptAync(string title = null, string description = null, public Task<string> DisplayPromptAync(string title = null, string description = null,
string text = null, string okButtonText = null, string cancelButtonText = null, string text = null, string okButtonText = null, string cancelButtonText = null,
bool numericKeyboard = false, bool autofocus = true, bool password = false) bool numericKeyboard = false, bool autofocus = true, bool password = false)
@@ -210,7 +413,10 @@ namespace Bit.Droid.Services
} }
if (numericKeyboard) if (numericKeyboard)
{ {
SetNumericKeyboardTo(input); input.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
#pragma warning disable CS0618 // Type or member is obsolete
input.KeyListener = DigitsKeyListener.GetInstance(false, false);
#pragma warning restore CS0618 // Type or member is obsolete
} }
if (password) if (password)
{ {
@@ -246,83 +452,6 @@ namespace Bit.Droid.Services
return result.Task; return result.Task;
} }
public Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config)
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity == null)
{
return Task.FromResult<ValidatablePromptResponse?>(null);
}
var alertBuilder = new AlertDialog.Builder(activity);
alertBuilder.SetTitle(config.Title);
var view = activity.LayoutInflater.Inflate(Resource.Layout.validatable_input_dialog_layout, null);
alertBuilder.SetView(view);
var result = new TaskCompletionSource<ValidatablePromptResponse?>();
alertBuilder.SetOnCancelListener(new BasicDialogWithResultCancelListener(result));
alertBuilder.SetPositiveButton(config.OkButtonText ?? AppResources.Ok, listener: null);
alertBuilder.SetNegativeButton(config.CancelButtonText ?? AppResources.Cancel, (sender, args) => result.TrySetResult(null));
if (!string.IsNullOrEmpty(config.ThirdButtonText))
{
alertBuilder.SetNeutralButton(config.ThirdButtonText, (sender, args) => result.TrySetResult(new ValidatablePromptResponse(null, true)));
}
var alert = alertBuilder.Create();
var input = view.FindViewById<EditText>(Resource.Id.txtValue);
var lblHeader = view.FindViewById<TextView>(Resource.Id.lblHeader);
var lblValueSubinfo = view.FindViewById<TextView>(Resource.Id.lblValueSubinfo);
lblHeader.Text = config.Subtitle;
lblValueSubinfo.Text = config.ValueSubInfo;
var defaultSubInfoColor = lblValueSubinfo.TextColors;
input.InputType = InputTypes.ClassText;
if (config.NumericKeyboard)
{
SetNumericKeyboardTo(input);
}
input.ImeOptions = input.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning | (ImeAction)ImeFlags.NoExtractUi;
input.Text = config.Text ?? string.Empty;
input.SetSelection(config.Text?.Length ?? 0);
input.AfterTextChanged += (sender, args) =>
{
if (lblValueSubinfo.Text != config.ValueSubInfo)
{
lblValueSubinfo.Text = config.ValueSubInfo;
lblHeader.SetTextColor(defaultSubInfoColor);
lblValueSubinfo.SetTextColor(defaultSubInfoColor);
}
};
alert.Window.SetSoftInputMode(SoftInput.StateVisible);
alert.Show();
var positiveButton = alert.GetButton((int)DialogButtonType.Positive);
positiveButton.Click += (sender, args) =>
{
var error = config.ValidateText(input.Text);
if (error != null)
{
lblHeader.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
lblValueSubinfo.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
lblValueSubinfo.Text = error;
lblValueSubinfo.SendAccessibilityEvent(Android.Views.Accessibility.EventTypes.ViewFocused);
return;
}
result.TrySetResult(new ValidatablePromptResponse(input.Text, false));
alert.Dismiss();
};
return result.Task;
}
public void RateApp() public void RateApp()
{ {
var activity = (MainActivity)CrossCurrentActivity.Current.Activity; var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
@@ -338,6 +467,34 @@ namespace Bit.Droid.Services
} }
} }
public void DisableAutofillService()
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
var manager = activity.GetSystemService(type) as AutofillManager;
manager.DisableAutofillServices();
}
catch { }
}
public bool AutofillServicesEnabled()
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
{
// Android 5-6: Both accessibility & overlay are required or nothing happens
return AutofillAccessibilityServiceRunning() && AutofillAccessibilityOverlayPermitted();
}
if (Build.VERSION.SdkInt == BuildVersionCodes.N)
{
// Android 7: Only accessibility is required (overlay is optional when using quick-action tile)
return AutofillAccessibilityServiceRunning();
}
// Android 8+: Either autofill or accessibility is required
return AutofillServiceEnabled() || AutofillAccessibilityServiceRunning();
}
public string GetBuildNumber() public string GetBuildNumber()
{ {
return Application.Context.ApplicationContext.PackageManager.GetPackageInfo( return Application.Context.ApplicationContext.PackageManager.GetPackageInfo(
@@ -369,6 +526,25 @@ namespace Bit.Droid.Services
return activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera); return activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera);
} }
public bool SupportsAutofillService()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return false;
}
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
var manager = activity.GetSystemService(type) as AutofillManager;
return manager.IsAutofillSupported;
}
catch
{
return false;
}
}
public int SystemMajorVersion() public int SystemMajorVersion()
{ {
return (int)Build.VERSION.SdkInt; return (int)Build.VERSION.SdkInt;
@@ -459,6 +635,112 @@ namespace Bit.Droid.Services
title, cancel, destruction, buttons); title, cancel, destruction, buttons);
} }
public void Autofill(CipherView cipher)
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity == null)
{
return;
}
if (activity.Intent?.GetBooleanExtra("autofillFramework", false) ?? false)
{
if (cipher == null)
{
activity.SetResult(Result.Canceled);
activity.Finish();
return;
}
var structure = activity.Intent.GetParcelableExtra(
AutofillManager.ExtraAssistStructure) as AssistStructure;
if (structure == null)
{
activity.SetResult(Result.Canceled);
activity.Finish();
return;
}
var parser = new Parser(structure, activity.ApplicationContext);
parser.Parse();
if ((!parser.FieldCollection?.Fields?.Any() ?? true) || string.IsNullOrWhiteSpace(parser.Uri))
{
activity.SetResult(Result.Canceled);
activity.Finish();
return;
}
var task = CopyTotpAsync(cipher);
var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher));
var replyIntent = new Intent();
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
activity.SetResult(Result.Ok, replyIntent);
activity.Finish();
var eventTask = _eventServiceFunc().CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
}
else
{
var data = new Intent();
if (cipher?.Login == null)
{
data.PutExtra("canceled", "true");
}
else
{
var task = CopyTotpAsync(cipher);
data.PutExtra("uri", cipher.Login.Uri);
data.PutExtra("username", cipher.Login.Username);
data.PutExtra("password", cipher.Login.Password);
}
if (activity.Parent == null)
{
activity.SetResult(Result.Ok, data);
}
else
{
activity.Parent.SetResult(Result.Ok, data);
}
activity.Finish();
_messagingService.Send("finishMainActivity");
if (cipher != null)
{
var eventTask = _eventServiceFunc().CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
}
}
}
public void CloseAutofill()
{
Autofill(null);
}
public void Background()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (activity.Intent?.GetBooleanExtra("autofillFramework", false) ?? false)
{
activity.SetResult(Result.Canceled);
activity.Finish();
}
else
{
activity.MoveTaskToBack(true);
}
}
public bool AutofillAccessibilityServiceRunning()
{
var enabledServices = Settings.Secure.GetString(Application.Context.ContentResolver,
Settings.Secure.EnabledAccessibilityServices);
return Application.Context.PackageName != null &&
(enabledServices?.Contains(Application.Context.PackageName) ?? false);
}
public bool AutofillAccessibilityOverlayPermitted()
{
return Accessibility.AccessibilityHelpers.OverlayPermitted();
}
public bool HasAutofillService()
{
return true;
}
public void OpenAccessibilityOverlayPermissionSettings() public void OpenAccessibilityOverlayPermissionSettings()
{ {
@@ -489,6 +771,25 @@ namespace Bit.Droid.Services
} }
} }
public bool AutofillServiceEnabled()
{
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
{
return false;
}
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var afm = (AutofillManager)activity.GetSystemService(
Java.Lang.Class.FromType(typeof(AutofillManager)));
return afm.IsEnabled && afm.HasEnabledAutofillServices;
}
catch
{
return false;
}
}
public void OpenAccessibilitySettings() public void OpenAccessibilitySettings()
{ {
try try
@@ -547,6 +848,61 @@ namespace Bit.Droid.Services
return true; return true;
} }
private bool DeleteDir(Java.IO.File dir)
{
if (dir != null && dir.IsDirectory)
{
var children = dir.List();
for (int i = 0; i < children.Length; i++)
{
var success = DeleteDir(new Java.IO.File(dir, children[i]));
if (!success)
{
return false;
}
}
return dir.Delete();
}
else if (dir != null && dir.IsFile)
{
return dir.Delete();
}
else
{
return false;
}
}
private bool HasPermission(string permission)
{
return ContextCompat.CheckSelfPermission(
CrossCurrentActivity.Current.Activity, permission) == Permission.Granted;
}
private void AskPermission(string permission)
{
ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission },
Constants.SelectFilePermissionRequestCode);
}
private List<IParcelable> GetCameraIntents(Android.Net.Uri outputUri)
{
var intents = new List<IParcelable>();
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
var captureIntent = new Intent(MediaStore.ActionImageCapture);
var listCam = pm.QueryIntentActivities(captureIntent, 0);
foreach (var res in listCam)
{
var packageName = res.ActivityInfo.PackageName;
var intent = new Intent(captureIntent);
intent.SetComponent(new ComponentName(packageName, res.ActivityInfo.Name));
intent.SetPackage(packageName);
intent.PutExtra(MediaStore.ExtraOutput, outputUri);
intents.Add(intent);
}
return intents;
}
private Intent RateIntentForUrl(string url, Activity activity) private Intent RateIntentForUrl(string url, Activity activity)
{ {
var intent = new Intent(Intent.ActionView, Android.Net.Uri.Parse($"{url}?id={activity.PackageName}")); var intent = new Intent(Intent.ActionView, Android.Net.Uri.Parse($"{url}?id={activity.PackageName}"));
@@ -564,6 +920,24 @@ namespace Bit.Droid.Services
return intent; return intent;
} }
private async Task CopyTotpAsync(CipherView cipher)
{
if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp))
{
var autoCopyDisabled = await _stateService.GetDisableAutoTotpCopyAsync();
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault())
{
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
if (totp != null)
{
await _clipboardService.CopyTextAsync(totp);
}
}
}
}
public float GetSystemFontSizeScale() public float GetSystemFontSizeScale()
{ {
var activity = CrossCurrentActivity.Current?.Activity as MainActivity; var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
@@ -577,6 +951,11 @@ namespace Bit.Droid.Services
public async Task SetScreenCaptureAllowedAsync() public async Task SetScreenCaptureAllowedAsync()
{ {
if (CoreHelpers.ForceScreenCaptureEnabled())
{
return;
}
var activity = CrossCurrentActivity.Current?.Activity; var activity = CrossCurrentActivity.Current?.Activity;
if (await _stateService.GetScreenCaptureAllowedAsync()) if (await _stateService.GetScreenCaptureAllowedAsync())
{ {
@@ -594,35 +973,5 @@ namespace Bit.Droid.Services
intent.SetData(uri); intent.SetData(uri);
Application.Context.StartActivity(intent); Application.Context.StartActivity(intent);
} }
public void CloseExtensionPopUp()
{
// only used by iOS
throw new NotImplementedException();
}
private void SetNumericKeyboardTo(EditText editText)
{
editText.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
#pragma warning disable CS0618 // Type or member is obsolete
editText.KeyListener = DigitsKeyListener.GetInstance(false, false);
#pragma warning restore CS0618 // Type or member is obsolete
}
}
class BasicDialogWithResultCancelListener : Java.Lang.Object, IDialogInterfaceOnCancelListener
{
private readonly TaskCompletionSource<ValidatablePromptResponse?> _taskCompletionSource;
public BasicDialogWithResultCancelListener(TaskCompletionSource<ValidatablePromptResponse?> taskCompletionSource)
{
_taskCompletionSource = taskCompletionSource;
}
public void OnCancel(IDialogInterface dialog)
{
_taskCompletionSource?.TrySetResult(null);
dialog?.Dismiss();
}
} }
} }

View File

@@ -1,278 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Android;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using Android.Provider;
using Android.Webkit;
using AndroidX.Core.App;
using AndroidX.Core.Content;
using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Plugin.CurrentActivity;
namespace Bit.Droid.Services
{
public class FileService : IFileService
{
private readonly IStateService _stateService;
private readonly IBroadcasterService _broadcasterService;
private bool _cameraPermissionsDenied;
public FileService(IStateService stateService, IBroadcasterService broadcasterService)
{
_stateService = stateService;
_broadcasterService = broadcasterService;
_broadcasterService.Subscribe(nameof(FileService), (message) =>
{
if (message.Command == "selectFileCameraPermissionDenied")
{
_cameraPermissionsDenied = true;
}
});
}
public bool OpenFile(byte[] fileData, string id, string fileName)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = BuildOpenFileIntent(fileData, fileName);
if (intent == null)
{
return false;
}
activity.StartActivity(intent);
return true;
}
catch { }
return false;
}
public bool CanOpenFile(string fileName)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var intent = BuildOpenFileIntent(new byte[0], string.Concat("opentest_", fileName));
if (intent == null)
{
return false;
}
var activities = activity.PackageManager.QueryIntentActivities(intent,
PackageInfoFlags.MatchDefaultOnly);
return (activities?.Count ?? 0) > 0;
}
catch { }
return false;
}
private Intent BuildOpenFileIntent(byte[] fileData, string fileName)
{
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if (extension == null)
{
return null;
}
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if (mimeType == null)
{
return null;
}
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var cachePath = activity.CacheDir;
var filePath = Path.Combine(cachePath.Path, fileName);
File.WriteAllBytes(filePath, fileData);
var file = new Java.IO.File(cachePath, fileName);
if (!file.IsFile)
{
return null;
}
try
{
var intent = new Intent(Intent.ActionView);
var uri = FileProvider.GetUriForFile(activity.ApplicationContext,
"com.x8bit.bitwarden.fileprovider", file);
intent.SetDataAndType(uri, mimeType);
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
return intent;
}
catch { }
return null;
}
public bool SaveFile(byte[] fileData, string id, string fileName, string contentUri)
{
try
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
if (contentUri != null)
{
var uri = Android.Net.Uri.Parse(contentUri);
var stream = activity.ContentResolver.OpenOutputStream(uri);
// Using java bufferedOutputStream due to this issue:
// https://github.com/xamarin/xamarin-android/issues/3498
var javaStream = new Java.IO.BufferedOutputStream(stream);
javaStream.Write(fileData);
javaStream.Flush();
javaStream.Close();
return true;
}
// Prompt for location to save file
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
if (extension == null)
{
return false;
}
string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
if (mimeType == null)
{
// Unable to identify so fall back to generic "any" type
mimeType = "*/*";
}
var intent = new Intent(Intent.ActionCreateDocument);
intent.SetType(mimeType);
intent.AddCategory(Intent.CategoryOpenable);
intent.PutExtra(Intent.ExtraTitle, fileName);
activity.StartActivityForResult(intent, Core.Constants.SaveFileRequestCode);
return true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace);
}
return false;
}
public async Task ClearCacheAsync()
{
try
{
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
}
catch (Exception) { }
}
public Task SelectFileAsync()
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var hasStorageWritePermission = !_cameraPermissionsDenied &&
HasPermission(Manifest.Permission.WriteExternalStorage);
var additionalIntents = new List<IParcelable>();
if (activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera))
{
var hasCameraPermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.Camera);
if (!_cameraPermissionsDenied && !hasStorageWritePermission)
{
AskPermission(Manifest.Permission.WriteExternalStorage);
return Task.FromResult(0);
}
if (!_cameraPermissionsDenied && !hasCameraPermission)
{
AskPermission(Manifest.Permission.Camera);
return Task.FromResult(0);
}
if (!_cameraPermissionsDenied && hasCameraPermission && hasStorageWritePermission)
{
try
{
var file = new Java.IO.File(activity.FilesDir, "temp_camera_photo.jpg");
if (!file.Exists())
{
file.ParentFile.Mkdirs();
file.CreateNewFile();
}
var outputFileUri = FileProvider.GetUriForFile(activity,
"com.x8bit.bitwarden.fileprovider", file);
additionalIntents.AddRange(GetCameraIntents(outputFileUri));
}
catch (Java.IO.IOException) { }
}
}
var docIntent = new Intent(Intent.ActionOpenDocument);
docIntent.AddCategory(Intent.CategoryOpenable);
docIntent.SetType("*/*");
var chooserIntent = Intent.CreateChooser(docIntent, AppResources.FileSource);
if (additionalIntents.Count > 0)
{
chooserIntent.PutExtra(Intent.ExtraInitialIntents, additionalIntents.ToArray());
}
activity.StartActivityForResult(chooserIntent, Core.Constants.SelectFileRequestCode);
return Task.FromResult(0);
}
private bool DeleteDir(Java.IO.File dir)
{
if (dir is null)
{
return false;
}
if (dir.IsDirectory)
{
var children = dir.List();
for (int i = 0; i < children.Length; i++)
{
var success = DeleteDir(new Java.IO.File(dir, children[i]));
if (!success)
{
return false;
}
}
return dir.Delete();
}
if (dir.IsFile)
{
return dir.Delete();
}
return false;
}
private bool HasPermission(string permission)
{
return ContextCompat.CheckSelfPermission(
CrossCurrentActivity.Current.Activity, permission) == Permission.Granted;
}
private void AskPermission(string permission)
{
ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission },
Core.Constants.SelectFilePermissionRequestCode);
}
private List<IParcelable> GetCameraIntents(Android.Net.Uri outputUri)
{
var intents = new List<IParcelable>();
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
var captureIntent = new Intent(MediaStore.ActionImageCapture);
var listCam = pm.QueryIntentActivities(captureIntent, 0);
foreach (var res in listCam)
{
var packageName = res.ActivityInfo.PackageName;
var intent = new Intent(captureIntent);
intent.SetComponent(new ComponentName(packageName, res.ActivityInfo.Name));
intent.SetPackage(packageName);
intent.PutExtra(MediaStore.ExtraOutput, outputUri);
intents.Add(intent);
}
return intents;
}
}
}

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
@@ -26,13 +25,13 @@ namespace Bit.Droid.Services
try try
{ {
var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage)); var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
Debug.WriteLine(netLanguage + " failed, trying " + fallback + " (" + e1.Message + ")"); Console.WriteLine(netLanguage + " failed, trying " + fallback + " (" + e1.Message + ")");
ci = new CultureInfo(fallback); ci = new CultureInfo(fallback);
} }
catch (CultureNotFoundException e2) catch (CultureNotFoundException e2)
{ {
// iOS language not valid .NET culture, falling back to English // iOS language not valid .NET culture, falling back to English
Debug.WriteLine(netLanguage + " couldn't be set, using 'en' (" + e2.Message + ")"); Console.WriteLine(netLanguage + " couldn't be set, using 'en' (" + e2.Message + ")");
ci = new CultureInfo("en"); ci = new CultureInfo("en");
} }
} }
@@ -41,7 +40,7 @@ namespace Bit.Droid.Services
private string AndroidToDotnetLanguage(string androidLanguage) private string AndroidToDotnetLanguage(string androidLanguage)
{ {
Debug.WriteLine("Android Language:" + androidLanguage); Console.WriteLine("Android Language:" + androidLanguage);
var netLanguage = androidLanguage; var netLanguage = androidLanguage;
if (androidLanguage.StartsWith("zh")) if (androidLanguage.StartsWith("zh"))
{ {
@@ -80,13 +79,13 @@ namespace Bit.Droid.Services
// ONLY use cultures that have been tested and known to work // ONLY use cultures that have been tested and known to work
} }
} }
Debug.WriteLine(".NET Language/Locale:" + netLanguage); Console.WriteLine(".NET Language/Locale:" + netLanguage);
return netLanguage; return netLanguage;
} }
private string ToDotnetFallbackLanguage(PlatformCulture platCulture) private string ToDotnetFallbackLanguage(PlatformCulture platCulture)
{ {
Debug.WriteLine(".NET Fallback Language:" + platCulture.LanguageCode); Console.WriteLine(".NET Fallback Language:" + platCulture.LanguageCode);
var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually); var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually);
switch (platCulture.LanguageCode) switch (platCulture.LanguageCode)
{ {
@@ -96,7 +95,7 @@ namespace Bit.Droid.Services
// add more application-specific cases here (if required) // add more application-specific cases here (if required)
// ONLY use cultures that have been tested and known to work // ONLY use cultures that have been tested and known to work
} }
Debug.WriteLine(".NET Fallback Language/Locale:" + netLanguage + " (application-specific)"); Console.WriteLine(".NET Fallback Language/Locale:" + netLanguage + " (application-specific)");
return netLanguage; return netLanguage;
} }

View File

@@ -1,28 +0,0 @@
using System;
using System.Threading.Tasks;
using Bit.App.Services;
using Bit.Core.Abstractions;
namespace Bit.Droid.Services
{
public class WatchDeviceService : BaseWatchDeviceService
{
public WatchDeviceService(ICipherService cipherService,
IEnvironmentService environmentService,
IStateService stateService,
IVaultTimeoutService vaultTimeoutService)
: base(cipherService, environmentService, stateService, vaultTimeoutService)
{
}
protected override bool IsSupported => false;
public override bool IsConnected => false;
protected override bool CanSendData => false;
protected override Task SendDataToWatchAsync(byte[] rawData) => throw new NotImplementedException();
protected override void ConnectToWatch() => throw new NotImplementedException();
}
}

View File

@@ -1,6 +1,5 @@
using Android.Content; using Android.Content;
using Android.OS; using Android.OS;
using Java.Lang;
namespace Bit.Droid.Utilities namespace Bit.Droid.Utilities
{ {
@@ -14,12 +13,7 @@ namespace Bit.Droid.Utilities
// Note: getting the bundle like this will cause to call unparcel() internally // Note: getting the bundle like this will cause to call unparcel() internally
var b = intent?.Extras?.GetBundle("trashstringwhichhasnousebuttocheckunparcel"); var b = intent?.Extras?.GetBundle("trashstringwhichhasnousebuttocheckunparcel");
} }
catch (Exception ex) when catch (BadParcelableException)
(
ex is BadParcelableException ||
ex is ClassNotFoundException ||
ex is RuntimeException
)
{ {
intent.ReplaceExtras((Bundle)null); intent.ReplaceExtras((Bundle)null);
} }

View File

@@ -62,7 +62,7 @@ namespace Bit.Droid.Utilities
theme = ThemeManager.Dark; theme = ThemeManager.Dark;
} }
if (theme == ThemeManager.Dark || theme == ThemeManager.Black || theme == ThemeManager.Nord || theme == ThemeManager.SolarizedDark) if (theme == ThemeManager.Dark || theme == ThemeManager.Black || theme == ThemeManager.Nord)
{ {
LightTheme = false; LightTheme = false;
} }

Binary file not shown.

Binary file not shown.

View File

@@ -8,8 +8,6 @@ namespace Bit.App.Abstractions
{ {
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost); void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
Task NavigateOnAccountChangeAsync(bool? isAuthed = null); Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
Task StartDefaultNavigationFlowAsync(Action<AppOptions> appOptionsAction);
Task LogOutAsync(string userId, bool userInitiated, bool expired); Task LogOutAsync(string userId, bool userInitiated, bool expired);
Task PromptToSwitchToExistingAccountAsync(string userId);
} }
} }

View File

@@ -1,9 +0,0 @@
using System;
namespace Bit.App.Abstractions
{
public interface IDeepLinkContext
{
bool OnNewUri(Uri uri);
}
}

View File

@@ -1,7 +1,6 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Utilities.Prompts;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models; using Bit.Core.Models.View;
namespace Bit.App.Abstractions namespace Bit.App.Abstractions
{ {
@@ -9,37 +8,47 @@ namespace Bit.App.Abstractions
{ {
string DeviceUserAgent { get; } string DeviceUserAgent { get; }
DeviceType DeviceType { get; } DeviceType DeviceType { get; }
int SystemMajorVersion();
string SystemModel();
string GetBuildNumber();
void Toast(string text, bool longDuration = false); void Toast(string text, bool longDuration = false);
bool LaunchApp(string appName);
Task ShowLoadingAsync(string text); Task ShowLoadingAsync(string text);
Task HideLoadingAsync(); Task HideLoadingAsync();
bool OpenFile(byte[] fileData, string id, string fileName);
bool SaveFile(byte[] fileData, string id, string fileName, string contentUri);
bool CanOpenFile(string fileName);
Task ClearCacheAsync();
Task SelectFileAsync();
Task<string> DisplayPromptAync(string title = null, string description = null, string text = null, Task<string> DisplayPromptAync(string title = null, string description = null, string text = null,
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false, string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
bool autofocus = true, bool password = false); bool autofocus = true, bool password = false);
Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config); void RateApp();
Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
bool SupportsFaceBiometric(); bool SupportsFaceBiometric();
Task<bool> SupportsFaceBiometricAsync(); Task<bool> SupportsFaceBiometricAsync();
bool SupportsNfc(); bool SupportsNfc();
bool SupportsCamera(); bool SupportsCamera();
bool SupportsFido2(); bool SupportsAutofillService();
int SystemMajorVersion();
bool LaunchApp(string appName); string SystemModel();
void RateApp(); Task<string> DisplayAlertAsync(string title, string message, string cancel, params string[] buttons);
Task<string> DisplayActionSheetAsync(string title, string cancel, string destruction, params string[] buttons);
void Autofill(CipherView cipher);
void CloseAutofill();
void Background();
bool AutofillAccessibilityServiceRunning();
bool AutofillAccessibilityOverlayPermitted();
bool HasAutofillService();
bool AutofillServiceEnabled();
void DisableAutofillService();
bool AutofillServicesEnabled();
string GetBuildNumber();
void OpenAccessibilitySettings(); void OpenAccessibilitySettings();
void OpenAccessibilityOverlayPermissionSettings(); void OpenAccessibilityOverlayPermissionSettings();
void OpenAutofillSettings(); void OpenAutofillSettings();
long GetActiveTime(); long GetActiveTime();
void CloseMainApp(); void CloseMainApp();
bool SupportsFido2();
float GetSystemFontSizeScale(); float GetSystemFontSizeScale();
Task OnAccountSwitchCompleteAsync(); Task OnAccountSwitchCompleteAsync();
Task SetScreenCaptureAllowedAsync(); Task SetScreenCaptureAllowedAsync();
void OpenAppSettings(); void OpenAppSettings();
void CloseExtensionPopUp();
} }
} }

View File

@@ -1,5 +1,4 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Models;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace Bit.App.Abstractions namespace Bit.App.Abstractions
@@ -10,8 +9,6 @@ namespace Bit.App.Abstractions
Task OnRegisteredAsync(string token, string device); Task OnRegisteredAsync(string token, string device);
void OnUnregistered(string device); void OnUnregistered(string device);
void OnError(string message, string device); void OnError(string message, string device);
Task OnNotificationTapped(BaseNotificationData data);
Task OnNotificationDismissed(BaseNotificationData data);
bool ShouldShowNotification(); bool ShouldShowNotification();
} }
} }

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Models;
namespace Bit.App.Abstractions namespace Bit.App.Abstractions
{ {
@@ -11,7 +10,7 @@ namespace Bit.App.Abstractions
Task<string> GetTokenAsync(); Task<string> GetTokenAsync();
Task RegisterAsync(); Task RegisterAsync();
Task UnregisterAsync(); Task UnregisterAsync();
void SendLocalNotification(string title, string message, BaseNotificationData data); void SendLocalNotification(string title, string message, string notificationId);
void DismissLocalNotification(string notificationId); void DismissLocalNotification(string notificationId);
} }
} }

View File

@@ -14,14 +14,14 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Plugin.Fingerprint" Version="2.1.5" /> <PackageReference Include="Plugin.Fingerprint" Version="2.1.5" />
<PackageReference Include="SkiaSharp.Views.Forms" Version="2.88.3" /> <PackageReference Include="SkiaSharp.Views.Forms" Version="2.88.2" />
<PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.6" /> <PackageReference Include="Xamarin.CommunityToolkit" Version="2.0.5" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.5" /> <PackageReference Include="Xamarin.Essentials" Version="1.7.3" />
<PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" /> <PackageReference Include="Xamarin.FFImageLoading.Forms" Version="2.4.11.982" />
<PackageReference Include="Xamarin.Forms" Version="5.0.0.2578" /> <PackageReference Include="Xamarin.Forms" Version="5.0.0.2515" />
<PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" /> <PackageReference Include="ZXing.Net.Mobile" Version="2.4.1" />
<PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" /> <PackageReference Include="ZXing.Net.Mobile.Forms" Version="2.4.1" />
<PackageReference Include="MessagePack" Version="2.4.59" /> <PackageReference Include="Xamarin.CommunityToolkit" Version="1.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -126,9 +126,6 @@
<Compile Update="Pages\Accounts\LoginPasswordlessPage.xaml.cs"> <Compile Update="Pages\Accounts\LoginPasswordlessPage.xaml.cs">
<DependentUpon>LoginPasswordlessPage.xaml</DependentUpon> <DependentUpon>LoginPasswordlessPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Update="Pages\Accounts\LoginPasswordlessRequestPage.xaml.cs">
<DependentUpon>LoginPasswordlessRequestPage.xaml</DependentUpon>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -143,10 +140,6 @@
<Folder Include="Controls\AccountSwitchingOverlay\" /> <Folder Include="Controls\AccountSwitchingOverlay\" />
<Folder Include="Utilities\AccountManagement\" /> <Folder Include="Utilities\AccountManagement\" />
<Folder Include="Controls\DateTime\" /> <Folder Include="Controls\DateTime\" />
<Folder Include="Controls\IconLabelButton\" />
<Folder Include="Controls\PasswordStrengthProgressBar\" />
<Folder Include="Utilities\Automation\" />
<Folder Include="Utilities\Prompts\" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@@ -438,11 +431,5 @@
<None Remove="Controls\AccountSwitchingOverlay\" /> <None Remove="Controls\AccountSwitchingOverlay\" />
<None Remove="Utilities\AccountManagement\" /> <None Remove="Utilities\AccountManagement\" />
<None Remove="Controls\DateTime\" /> <None Remove="Controls\DateTime\" />
<None Remove="Controls\IconLabelButton\" />
<None Remove="MessagePack" />
<None Remove="MessagePack.MSBuild.Tasks" />
<None Remove="Controls\PasswordStrengthProgressBar\" />
<None Remove="Utilities\Automation\" />
<None Remove="Utilities\Prompts\" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.App.Models; using Bit.App.Models;
@@ -12,7 +11,6 @@ using Bit.Core;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.Models.Response;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Xamarin.Forms; using Xamarin.Forms;
@@ -23,11 +21,6 @@ namespace Bit.App
{ {
public partial class App : Application, IAccountsManagerHost public partial class App : Application, IAccountsManagerHost
{ {
public const string POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE = "popAllAndGoToTabGenerator";
public const string POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE = "popAllAndGoToTabMyVault";
public const string POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE = "popAllAndGoToTabSend";
public const string POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE = "popAllAndGoToAutofillCiphers";
private readonly IBroadcasterService _broadcasterService; private readonly IBroadcasterService _broadcasterService;
private readonly IMessagingService _messagingService; private readonly IMessagingService _messagingService;
private readonly IStateService _stateService; private readonly IStateService _stateService;
@@ -35,14 +28,11 @@ namespace Bit.App
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private readonly IAuthService _authService; private readonly IAuthService _authService;
private readonly IDeviceActionService _deviceActionService; private readonly IDeviceActionService _deviceActionService;
private readonly IFileService _fileService;
private readonly IAccountsManager _accountsManager; private readonly IAccountsManager _accountsManager;
private readonly IPushNotificationService _pushNotificationService; private readonly IPushNotificationService _pushNotificationService;
private readonly IConfigService _configService;
private static bool _isResumed; private static bool _isResumed;
// these variables are static because the app is launching new activities on notification click, creating new instances of App. // this variable is static because the app is launching new activities on notification click, creating new instances of App.
private static bool _pendingCheckPasswordlessLoginRequests; private static bool _pendingCheckPasswordlessLoginRequests;
private static object _processingLoginRequestLock = new object();
public App(AppOptions appOptions) public App(AppOptions appOptions)
{ {
@@ -59,10 +49,8 @@ namespace Bit.App
_syncService = ServiceContainer.Resolve<ISyncService>("syncService"); _syncService = ServiceContainer.Resolve<ISyncService>("syncService");
_authService = ServiceContainer.Resolve<IAuthService>("authService"); _authService = ServiceContainer.Resolve<IAuthService>("authService");
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"); _deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
_fileService = ServiceContainer.Resolve<IFileService>();
_accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager"); _accountsManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
_pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>(); _pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>();
_configService = ServiceContainer.Resolve<IConfigService>();
_accountsManager.Init(() => Options, this); _accountsManager.Init(() => Options, this);
@@ -110,18 +98,12 @@ namespace Bit.App
await Task.Delay(1000); await Task.Delay(1000);
await _accountsManager.NavigateOnAccountChangeAsync(); await _accountsManager.NavigateOnAccountChangeAsync();
} }
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE || else if (message.Command == "popAllAndGoToTabGenerator" ||
message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE || message.Command == "popAllAndGoToTabMyVault" ||
message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE || message.Command == "popAllAndGoToTabSend" ||
message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE || message.Command == "popAllAndGoToAutofillCiphers")
message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{ {
if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE) Device.BeginInvokeOnMainThread(async () =>
{
Options.OtpData = new OtpData((string)message.Data);
}
await Device.InvokeOnMainThreadAsync(async () =>
{ {
if (Current.MainPage is TabsPage tabsPage) if (Current.MainPage is TabsPage tabsPage)
{ {
@@ -129,29 +111,24 @@ namespace Bit.App
{ {
await tabsPage.Navigation.PopModalAsync(false); await tabsPage.Navigation.PopModalAsync(false);
} }
if (message.Command == POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE) if (message.Command == "popAllAndGoToAutofillCiphers")
{ {
Current.MainPage = new NavigationPage(new CipherSelectionPage(Options)); Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
} }
else if (message.Command == POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE) else if (message.Command == "popAllAndGoToTabMyVault")
{ {
Options.MyVaultTile = false; Options.MyVaultTile = false;
tabsPage.ResetToVaultPage(); tabsPage.ResetToVaultPage();
} }
else if (message.Command == POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE) else if (message.Command == "popAllAndGoToTabGenerator")
{ {
Options.GeneratorTile = false; Options.GeneratorTile = false;
tabsPage.ResetToGeneratorPage(); tabsPage.ResetToGeneratorPage();
} }
else if (message.Command == POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE) else if (message.Command == "popAllAndGoToTabSend")
{ {
tabsPage.ResetToSendPage(); tabsPage.ResetToSendPage();
} }
else if (message.Command == DeepLinkContext.NEW_OTP_MESSAGE)
{
tabsPage.ResetToVaultPage();
await tabsPage.Navigation.PushModalAsync(new NavigationPage(new CipherSelectionPage(Options)));
}
} }
}); });
} }
@@ -163,27 +140,9 @@ namespace Bit.App
new NavigationPage(new RemoveMasterPasswordPage())); new NavigationPage(new RemoveMasterPasswordPage()));
}); });
} }
else if (message.Command == Constants.ForceUpdatePassword) else if (message.Command == "passwordlessLoginRequest" || message.Command == "unlocked" || message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
{ {
Device.BeginInvokeOnMainThread(async () => CheckPasswordlessLoginRequestsAsync().FireAndForget();
{
await Application.Current.MainPage.Navigation.PushModalAsync(
new NavigationPage(new UpdateTempPasswordPage()));
});
}
else if (message.Command == "syncCompleted")
{
await _configService.GetAsync(true);
}
else if (message.Command == Constants.PasswordlessLoginRequestKey
|| message.Command == "unlocked"
|| message.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED)
{
lock (_processingLoginRequestLock)
{
// lock doesn't allow for async execution
CheckPasswordlessLoginRequestsAsync().Wait();
}
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -200,30 +159,22 @@ namespace Bit.App
_pendingCheckPasswordlessLoginRequests = true; _pendingCheckPasswordlessLoginRequests = true;
return; return;
} }
_pendingCheckPasswordlessLoginRequests = false; _pendingCheckPasswordlessLoginRequests = false;
if (await _vaultTimeoutService.IsLockedAsync()) if (await _vaultTimeoutService.IsLockedAsync())
{ {
return; return;
} }
var notification = await _stateService.GetPasswordlessLoginNotificationAsync(); var notification = await _stateService.GetPasswordlessLoginNotificationAsync();
if (notification == null) if (notification == null)
{ {
return; return;
} }
if (await CheckShouldSwitchActiveUserAsync(notification))
{
return;
}
// Delay to wait for the vault page to appear // Delay to wait for the vault page to appear
await Task.Delay(2000); await Task.Delay(2000);
// if there is a request modal opened ignore all incoming requests
if (App.Current.MainPage.Navigation.ModalStack.Any(p => p is NavigationPage navPage && navPage.CurrentPage is LoginPasswordlessPage))
{
return;
}
var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(notification.Id); var loginRequestData = await _authService.GetPasswordlessLoginRequestByIdAsync(notification.Id);
var page = new LoginPasswordlessPage(new LoginPasswordlessDetails() var page = new LoginPasswordlessPage(new LoginPasswordlessDetails()
{ {
@@ -231,47 +182,19 @@ namespace Bit.App
Id = loginRequestData.Id, Id = loginRequestData.Id,
IpAddress = loginRequestData.RequestIpAddress, IpAddress = loginRequestData.RequestIpAddress,
Email = await _stateService.GetEmailAsync(), Email = await _stateService.GetEmailAsync(),
FingerprintPhrase = loginRequestData.FingerprintPhrase, FingerprintPhrase = loginRequestData.RequestFingerprint,
RequestDate = loginRequestData.CreationDate, RequestDate = loginRequestData.CreationDate,
DeviceType = loginRequestData.RequestDeviceType, DeviceType = loginRequestData.RequestDeviceType,
Origin = loginRequestData.Origin Origin = loginRequestData.Origin,
}); });
await _stateService.SetPasswordlessLoginNotificationAsync(null); await _stateService.SetPasswordlessLoginNotificationAsync(null);
_pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId); _pushNotificationService.DismissLocalNotification(Constants.PasswordlessNotificationId);
if (!loginRequestData.IsExpired) if (loginRequestData.CreationDate.AddMinutes(Constants.PasswordlessNotificationTimeoutInMinutes) > DateTime.UtcNow)
{ {
await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page))); await Device.InvokeOnMainThreadAsync(() => Application.Current.MainPage.Navigation.PushModalAsync(new NavigationPage(page)));
} }
} }
private async Task<bool> CheckShouldSwitchActiveUserAsync(PasswordlessRequestNotification notification)
{
var activeUserId = await _stateService.GetActiveUserIdAsync();
if (notification.UserId == activeUserId)
{
return false;
}
var notificationUserEmail = await _stateService.GetEmailAsync(notification.UserId);
Device.BeginInvokeOnMainThread(async () =>
{
try
{
var result = await _deviceActionService.DisplayAlertAsync(AppResources.LogInRequested, string.Format(AppResources.LoginAttemptFromXDoYouWantToSwitchToThisAccount, notificationUserEmail), AppResources.Cancel, AppResources.Ok);
if (result == AppResources.Ok)
{
await _stateService.SetActiveUserAsync(notification.UserId);
_messagingService.Send(AccountsManagerMessageCommands.SWITCHED_ACCOUNT);
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
return true;
}
public AppOptions Options { get; private set; } public AppOptions Options { get; private set; }
protected async override void OnStart() protected async override void OnStart()
@@ -291,7 +214,7 @@ namespace Bit.App
} }
if (_pendingCheckPasswordlessLoginRequests) if (_pendingCheckPasswordlessLoginRequests)
{ {
_messagingService.Send(Constants.PasswordlessLoginRequestKey); CheckPasswordlessLoginRequestsAsync().FireAndForget();
} }
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
@@ -299,8 +222,6 @@ namespace Bit.App
// Reset delay on every start // Reset delay on every start
_vaultTimeoutService.DelayLockAndLogoutMs = null; _vaultTimeoutService.DelayLockAndLogoutMs = null;
} }
await _configService.GetAsync();
_messagingService.Send("startEventTimer"); _messagingService.Send("startEventTimer");
} }
@@ -329,7 +250,7 @@ namespace Bit.App
_isResumed = true; _isResumed = true;
if (_pendingCheckPasswordlessLoginRequests) if (_pendingCheckPasswordlessLoginRequests)
{ {
_messagingService.Send(Constants.PasswordlessLoginRequestKey); CheckPasswordlessLoginRequestsAsync().FireAndForget();
} }
if (Device.RuntimePlatform == Device.Android) if (Device.RuntimePlatform == Device.Android)
{ {
@@ -340,7 +261,6 @@ namespace Bit.App
private async Task SleptAsync() private async Task SleptAsync()
{ {
await _vaultTimeoutService.CheckVaultTimeoutAsync(); await _vaultTimeoutService.CheckVaultTimeoutAsync();
await ClearSensitiveFieldsAsync();
_messagingService.Send("stopEventTimer"); _messagingService.Send("stopEventTimer");
} }
@@ -348,7 +268,6 @@ namespace Bit.App
{ {
await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync(); await _stateService.CheckExtensionActiveUserAndSwitchIfNeededAsync();
await _vaultTimeoutService.CheckVaultTimeoutAsync(); await _vaultTimeoutService.CheckVaultTimeoutAsync();
await ClearSensitiveFieldsAsync();
_messagingService.Send("startEventTimer"); _messagingService.Send("startEventTimer");
await UpdateThemeAsync(); await UpdateThemeAsync();
await ClearCacheIfNeededAsync(); await ClearCacheIfNeededAsync();
@@ -369,14 +288,6 @@ namespace Bit.App
}); });
} }
private async Task ClearSensitiveFieldsAsync()
{
await Device.InvokeOnMainThreadAsync(() =>
{
_messagingService.Send(Constants.ClearSensitiveFields);
});
}
private void SetCulture() private void SetCulture()
{ {
// Calendars are removed by linker. ref https://bugzilla.xamarin.com/show_bug.cgi?id=59077 // Calendars are removed by linker. ref https://bugzilla.xamarin.com/show_bug.cgi?id=59077
@@ -390,7 +301,7 @@ namespace Bit.App
var lastClear = await _stateService.GetLastFileCacheClearAsync(); var lastClear = await _stateService.GetLastFileCacheClearAsync();
if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1) if ((DateTime.UtcNow - lastClear.GetValueOrDefault(DateTime.MinValue)).TotalDays >= 1)
{ {
var task = Task.Run(() => _fileService.ClearCacheAsync()); var task = Task.Run(() => _deviceActionService.ClearCacheAsync());
} }
} }
@@ -526,8 +437,7 @@ namespace Bit.App
Current.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options)); Current.MainPage = new NavigationPage(new CipherAddEditPage(appOptions: Options));
break; break;
case NavigationTarget.AutofillCiphers: case NavigationTarget.AutofillCiphers:
case NavigationTarget.OtpCipherSelection: Current.MainPage = new NavigationPage(new AutofillCiphersPage(Options));
Current.MainPage = new NavigationPage(new CipherSelectionPage(Options));
break; break;
case NavigationTarget.SendAddEdit: case NavigationTarget.SendAddEdit:
Current.MainPage = new NavigationPage(new SendAddEditPage(Options)); Current.MainPage = new NavigationPage(new SendAddEditPage(Options));

View File

@@ -30,15 +30,13 @@
BackgroundColor="{DynamicResource BackgroundColor}" BackgroundColor="{DynamicResource BackgroundColor}"
VerticalOptions="Start" VerticalOptions="Start"
RowHeight="{Binding AccountListRowHeight, Source={x:Reference _mainOverlay}}" RowHeight="{Binding AccountListRowHeight, Source={x:Reference _mainOverlay}}"
effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never" effects:ScrollViewContentInsetAdjustmentBehaviorEffect.ContentInsetAdjustmentBehavior="Never">
AutomationId="AccountListView">
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate x:DataType="view:AccountView"> <DataTemplate x:DataType="view:AccountView">
<controls:AccountViewCell <controls:AccountViewCell
Account="{Binding .}" Account="{Binding .}"
SelectAccountCommand="{Binding SelectAccountCommand, Source={x:Reference _mainOverlay}}" SelectAccountCommand="{Binding SelectAccountCommand, Source={x:Reference _mainOverlay}}"
LongPressAccountCommand="{Binding LongPressAccountCommand, Source={x:Reference _mainOverlay}}" LongPressAccountCommand="{Binding LongPressAccountCommand, Source={x:Reference _mainOverlay}}"
AutomationId="AccountViewCell"
/> />
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ViewCell xmlns="http://xamarin.com/schemas/2014/forms" <ViewCell xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit" xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
@@ -60,23 +60,20 @@
Text="{Binding AccountView.Email}" Text="{Binding AccountView.Email}"
IsVisible="{Binding IsActive}" IsVisible="{Binding IsActive}"
StyleClass="accountlist-title, accountlist-title-platform" StyleClass="accountlist-title, accountlist-title-platform"
LineBreakMode="TailTruncation" LineBreakMode="TailTruncation" />
AutomationId="AccountEmailLabel" />
<Label <Label
Grid.Row="0" Grid.Row="0"
Text="{Binding AccountView.Email}" Text="{Binding AccountView.Email}"
IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}" IsVisible="{Binding IsActive, Converter={StaticResource inverseBool}}"
StyleClass="accountlist-title, accountlist-title-platform" StyleClass="accountlist-title, accountlist-title-platform"
TextColor="{DynamicResource MutedColor}" TextColor="{DynamicResource MutedColor}"
LineBreakMode="TailTruncation" LineBreakMode="TailTruncation" />
AutomationId="AccountEmailLabel" />
<Label <Label
Grid.Row="1" Grid.Row="1"
IsVisible="{Binding ShowHostname}" IsVisible="{Binding ShowHostname}"
Text="{Binding AccountView.Hostname}" Text="{Binding AccountView.Hostname}"
StyleClass="accountlist-sub, accountlist-sub-platform" StyleClass="accountlist-sub, accountlist-sub-platform"
LineBreakMode="TailTruncation" LineBreakMode="TailTruncation" />
AutomationId="AccountHostUrlLabel" />
<Label <Label
Grid.Row="2" Grid.Row="2"
Text="{u:I18n AccountUnlocked}" Text="{u:I18n AccountUnlocked}"
@@ -84,8 +81,7 @@
StyleClass="accountlist-sub, accountlist-sub-platform" StyleClass="accountlist-sub, accountlist-sub-platform"
FontAttributes="Italic" FontAttributes="Italic"
TextTransform="Lowercase" TextTransform="Lowercase"
LineBreakMode="TailTruncation" LineBreakMode="TailTruncation" />
AutomationId="AccountStatusLabel" />
<Label <Label
Grid.Row="2" Grid.Row="2"
Text="{u:I18n AccountLocked}" Text="{u:I18n AccountLocked}"
@@ -93,8 +89,7 @@
StyleClass="accountlist-sub, accountlist-sub-platform" StyleClass="accountlist-sub, accountlist-sub-platform"
FontAttributes="Italic" FontAttributes="Italic"
TextTransform="Lowercase" TextTransform="Lowercase"
LineBreakMode="TailTruncation" LineBreakMode="TailTruncation" />
AutomationId="AccountStatusLabel" />
<Label <Label
Grid.Row="2" Grid.Row="2"
Text="{u:I18n AccountLoggedOut}" Text="{u:I18n AccountLoggedOut}"
@@ -102,8 +97,7 @@
StyleClass="accountlist-sub, accountlist-sub-platform" StyleClass="accountlist-sub, accountlist-sub-platform"
FontAttributes="Italic" FontAttributes="Italic"
TextTransform="Lowercase" TextTransform="Lowercase"
LineBreakMode="TailTruncation" LineBreakMode="TailTruncation" />
AutomationId="AccountStatusLabel" />
</Grid> </Grid>
<controls:IconLabel <controls:IconLabel
@@ -113,8 +107,7 @@
Margin="12,0" Margin="12,0"
HorizontalOptions="Center" HorizontalOptions="Center"
VerticalOptions="Center" VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform" StyleClass="list-icon, list-icon-platform" />
AutomationId="InactiveVaultIcon" />
<controls:IconLabel <controls:IconLabel
Grid.Column="2" Grid.Column="2"
Text="{Binding AuthStatusIconActive}" Text="{Binding AuthStatusIconActive}"
@@ -123,8 +116,7 @@
HorizontalOptions="Center" HorizontalOptions="Center"
VerticalOptions="Center" VerticalOptions="Center"
StyleClass="list-icon, list-icon-platform" StyleClass="list-icon, list-icon-platform"
TextColor="{DynamicResource TextColor}" TextColor="{DynamicResource TextColor}"/>
AutomationId="ActiveVaultIcon" />
</Grid> </Grid>
<Grid <Grid
@@ -155,8 +147,7 @@
StyleClass="accountlist-title, accountlist-title-platform" StyleClass="accountlist-title, accountlist-title-platform"
LineBreakMode="TailTruncation" LineBreakMode="TailTruncation"
VerticalOptions="Center" VerticalOptions="Center"
Grid.Column="1" Grid.Column="1" />
AutomationId="AddAccountButton" />
</Grid> </Grid>
</Grid> </Grid>
</ViewCell> </ViewCell>

View File

@@ -14,7 +14,7 @@ namespace Bit.App.Controls
{ {
AccountView = accountView; AccountView = accountView;
AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool") AvatarImageSource = ServiceContainer.Resolve<IAvatarImageSourcePool>("avatarImageSourcePool")
?.GetOrCreateAvatar(AccountView.UserId, AccountView.Name, AccountView.Email, AccountView.AvatarColor); ?.GetOrCreateAvatar(AccountView.Name, AccountView.Email);
} }
public AccountView AccountView public AccountView AccountView

View File

@@ -3,7 +3,6 @@ using System.IO;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Bit.Core.Utilities;
using SkiaSharp; using SkiaSharp;
using Xamarin.Forms; using Xamarin.Forms;
@@ -11,9 +10,7 @@ namespace Bit.App.Controls
{ {
public class AvatarImageSource : StreamImageSource public class AvatarImageSource : StreamImageSource
{ {
private readonly string _text; private string _data;
private readonly string _id;
private readonly string _color;
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
@@ -24,23 +21,21 @@ namespace Bit.App.Controls
if (obj is AvatarImageSource avatar) if (obj is AvatarImageSource avatar)
{ {
return avatar._id == _id && avatar._text == _text && avatar._color == _color; return avatar._data == _data;
} }
return base.Equals(obj); return base.Equals(obj);
} }
public override int GetHashCode() => _id?.GetHashCode() ?? _text?.GetHashCode() ?? -1; public override int GetHashCode() => _data?.GetHashCode() ?? -1;
public AvatarImageSource(string userId = null, string name = null, string email = null, string color = null) public AvatarImageSource(string name = null, string email = null)
{ {
_id = userId; _data = name;
_text = name; if (string.IsNullOrWhiteSpace(_data))
if (string.IsNullOrWhiteSpace(_text))
{ {
_text = email; _data = email;
} }
_color = color;
} }
public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync; public override Func<CancellationToken, Task<Stream>> Stream => GetStreamAsync;
@@ -57,24 +52,24 @@ namespace Bit.App.Controls
private Stream Draw() private Stream Draw()
{ {
string chars; string chars;
string upperCaseText = null; string upperData = null;
if (string.IsNullOrEmpty(_text)) if (string.IsNullOrEmpty(_data))
{ {
chars = ".."; chars = "..";
} }
else if (_text?.Length > 1) else if (_data?.Length > 1)
{ {
upperCaseText = _text.ToUpper(); upperData = _data.ToUpper();
chars = GetFirstLetters(upperCaseText, 2); chars = GetFirstLetters(upperData, 2);
} }
else else
{ {
chars = upperCaseText = _text.ToUpper(); chars = upperData = _data.ToUpper();
} }
var bgColor = _color ?? CoreHelpers.StringToColor(_id ?? upperCaseText, "#33ffffff"); var bgColor = StringToColor(upperData);
var textColor = CoreHelpers.TextColorFromBgColor(bgColor); var textColor = Color.White;
var size = 50; var size = 50;
using (var bitmap = new SKBitmap(size * 2, using (var bitmap = new SKBitmap(size * 2,
@@ -90,7 +85,7 @@ namespace Bit.App.Controls
IsAntialias = true, IsAntialias = true,
Style = SKPaintStyle.Fill, Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter, StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(bgColor) Color = SKColor.Parse(bgColor.ToHex())
}) })
{ {
var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2; var midX = canvas.LocalClipBounds.Size.ToSizeI().Width / 2;
@@ -102,7 +97,7 @@ namespace Bit.App.Controls
IsAntialias = true, IsAntialias = true,
Style = SKPaintStyle.Fill, Style = SKPaintStyle.Fill,
StrokeJoin = SKStrokeJoin.Miter, StrokeJoin = SKStrokeJoin.Miter,
Color = SKColor.Parse(bgColor) Color = SKColor.Parse(bgColor.ToHex())
}) })
{ {
canvas.DrawCircle(midX, midY, radius, circlePaint); canvas.DrawCircle(midX, midY, radius, circlePaint);
@@ -113,7 +108,7 @@ namespace Bit.App.Controls
{ {
IsAntialias = true, IsAntialias = true,
Style = SKPaintStyle.Fill, Style = SKPaintStyle.Fill,
Color = SKColor.Parse(textColor), Color = SKColor.Parse(textColor.ToHex()),
TextSize = textSize, TextSize = textSize,
TextAlign = SKTextAlign.Center, TextAlign = SKTextAlign.Center,
Typeface = typeface Typeface = typeface

View File

@@ -5,19 +5,19 @@ namespace Bit.App.Controls
{ {
public interface IAvatarImageSourcePool public interface IAvatarImageSourcePool
{ {
AvatarImageSource GetOrCreateAvatar(string userId, string name, string email, string color); AvatarImageSource GetOrCreateAvatar(string name, string email);
} }
public class AvatarImageSourcePool : IAvatarImageSourcePool public class AvatarImageSourcePool : IAvatarImageSourcePool
{ {
private readonly ConcurrentDictionary<string, AvatarImageSource> _cache = new ConcurrentDictionary<string, AvatarImageSource>(); private readonly ConcurrentDictionary<string, AvatarImageSource> _cache = new ConcurrentDictionary<string, AvatarImageSource>();
public AvatarImageSource GetOrCreateAvatar(string userId, string name, string email, string color) public AvatarImageSource GetOrCreateAvatar(string name, string email)
{ {
var key = $"{userId}{name}{email}{color}"; var key = $"{name}{email}";
if (!_cache.TryGetValue(key, out var avatar)) if (!_cache.TryGetValue(key, out var avatar))
{ {
avatar = new AvatarImageSource(userId, name, email, color); avatar = new AvatarImageSource(name, email);
if (!_cache.TryAdd(key, avatar) if (!_cache.TryAdd(key, avatar)
&& &&
!_cache.TryGetValue(key, out avatar)) // If add fails another thread created the avatar in between the first try get and the try add. !_cache.TryGetValue(key, out avatar)) // If add fails another thread created the avatar in between the first try get and the try add.

View File

@@ -9,8 +9,7 @@
StyleClass="list-row, list-row-platform" StyleClass="list-row, list-row-platform"
RowSpacing="0" RowSpacing="0"
ColumnSpacing="0" ColumnSpacing="0"
x:DataType="controls:CipherViewCellViewModel" x:DataType="controls:CipherViewCellViewModel">
AutomationId="CipherCell">
<Grid.Resources> <Grid.Resources>
<u:IconGlyphConverter x:Key="iconGlyphConverter"/> <u:IconGlyphConverter x:Key="iconGlyphConverter"/>
@@ -37,8 +36,7 @@
IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}" IsVisible="{Binding ShowIconImage, Converter={StaticResource inverseBool}}"
Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}" Text="{Binding Cipher, Converter={StaticResource iconGlyphConverter}}"
ShouldUpdateFontSizeDynamicallyForAccesibility="True" ShouldUpdateFontSizeDynamicallyForAccesibility="True"
AutomationProperties.IsInAccessibleTree="False" AutomationProperties.IsInAccessibleTree="False" />
AutomationId="CipherTypeIcon" />
<ff:CachedImage <ff:CachedImage
x:Name="_iconImage" x:Name="_iconImage"
@@ -54,8 +52,7 @@
Aspect="AspectFit" Aspect="AspectFit"
IsVisible="{Binding ShowIconImage}" IsVisible="{Binding ShowIconImage}"
Source="{Binding IconImageSource, Mode=OneTime}" Source="{Binding IconImageSource, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="False" AutomationProperties.IsInAccessibleTree="False" />
AutomationId="CipherWebsiteIcon" />
<Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7"> <Grid RowSpacing="0" ColumnSpacing="0" Grid.Row="0" Grid.Column="1" VerticalOptions="Center" Padding="0, 7">
<Grid.RowDefinitions> <Grid.RowDefinitions>
@@ -74,8 +71,7 @@
Grid.Column="0" Grid.Column="0"
Grid.Row="0" Grid.Row="0"
StyleClass="list-title, list-title-platform" StyleClass="list-title, list-title-platform"
Text="{Binding Cipher.Name}" Text="{Binding Cipher.Name}" />
AutomationId="CipherNameLabel" />
<Label <Label
LineBreakMode="TailTruncation" LineBreakMode="TailTruncation"
Grid.Column="0" Grid.Column="0"
@@ -84,8 +80,7 @@
StyleClass="list-subtitle, list-subtitle-platform" StyleClass="list-subtitle, list-subtitle-platform"
Text="{Binding Cipher.SubTitle}" Text="{Binding Cipher.SubTitle}"
IsVisible="{Binding Source={RelativeSource Self}, Path=Text, IsVisible="{Binding Source={RelativeSource Self}, Path=Text,
Converter={StaticResource stringHasValueConverter}}" Converter={StaticResource stringHasValueConverter}}"/>
AutomationId="CipherSubTitleLabel" />
<controls:IconLabel <controls:IconLabel
Grid.Column="1" Grid.Column="1"
Grid.Row="0" Grid.Row="0"
@@ -96,8 +91,7 @@
Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}" Text="{Binding Source={x:Static core:BitwardenIcons.Collection}}"
IsVisible="{Binding Cipher.Shared, Mode=OneTime}" IsVisible="{Binding Cipher.Shared, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Shared}" AutomationProperties.Name="{u:I18n Shared}" />
AutomationId="CipherInCollectionIcon" />
<controls:IconLabel <controls:IconLabel
Grid.Column="2" Grid.Column="2"
Grid.Row="0" Grid.Row="0"
@@ -108,8 +102,7 @@
Text="{Binding Source={x:Static core:BitwardenIcons.Paperclip}}" Text="{Binding Source={x:Static core:BitwardenIcons.Paperclip}}"
IsVisible="{Binding Cipher.HasAttachments, Mode=OneTime}" IsVisible="{Binding Cipher.HasAttachments, Mode=OneTime}"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Attachments}" AutomationProperties.Name="{u:I18n Attachments}" />
AutomationId="CipherWithAttachmentsIcon" />
</Grid> </Grid>
<controls:MiButton <controls:MiButton
@@ -121,7 +114,6 @@
VerticalOptions="CenterAndExpand" VerticalOptions="CenterAndExpand"
HorizontalOptions="EndAndExpand" HorizontalOptions="EndAndExpand"
AutomationProperties.IsInAccessibleTree="True" AutomationProperties.IsInAccessibleTree="True"
AutomationProperties.Name="{u:I18n Options}" AutomationProperties.Name="{u:I18n Options}" />
AutomationId="CipherOptionsButton" />
</controls:ExtendedGrid> </controls:ExtendedGrid>

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Windows.Input;
using Bit.App.Abstractions; using Bit.App.Abstractions;
using Bit.Core.Models.View; using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
@@ -19,7 +18,7 @@ namespace Bit.App.Controls
nameof(WebsiteIconsEnabled), typeof(bool?), typeof(CipherViewCell)); nameof(WebsiteIconsEnabled), typeof(bool?), typeof(CipherViewCell));
public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create( public static readonly BindableProperty ButtonCommandProperty = BindableProperty.Create(
nameof(ButtonCommand), typeof(ICommand), typeof(CipherViewCell)); nameof(ButtonCommand), typeof(Command<CipherView>), typeof(CipherViewCell));
public CipherViewCell() public CipherViewCell()
{ {
@@ -43,9 +42,9 @@ namespace Bit.App.Controls
set => SetValue(CipherProperty, value); set => SetValue(CipherProperty, value);
} }
public ICommand ButtonCommand public Command<CipherView> ButtonCommand
{ {
get => GetValue(ButtonCommandProperty) as ICommand; get => GetValue(ButtonCommandProperty) as Command<CipherView>;
set => SetValue(ButtonCommandProperty, value); set => SetValue(ButtonCommandProperty, value);
} }

View File

@@ -31,7 +31,7 @@ namespace Bit.App.Controls
public bool ShowIconImage public bool ShowIconImage
{ {
get => WebsiteIconsEnabled get => WebsiteIconsEnabled
&& !string.IsNullOrWhiteSpace(Cipher.LaunchUri) && !string.IsNullOrWhiteSpace(Cipher.Login?.Uri)
&& IconImageSource != null; && IconImageSource != null;
} }
@@ -41,7 +41,7 @@ namespace Bit.App.Controls
{ {
if (_iconImageSource == string.Empty) // default value since icon source can return null if (_iconImageSource == string.Empty) // default value since icon source can return null
{ {
_iconImageSource = IconImageHelper.GetIconImage(Cipher); _iconImageSource = IconImageHelper.GetLoginIconImage(Cipher);
} }
return _iconImageSource; return _iconImageSource;
} }

View File

@@ -21,13 +21,13 @@ namespace Bit.App.Controls
nameof(StrokeWidth), typeof(float), typeof(CircularProgressbarView), 3f); nameof(StrokeWidth), typeof(float), typeof(CircularProgressbarView), 3f);
public static readonly BindableProperty ProgressColorProperty = BindableProperty.Create( public static readonly BindableProperty ProgressColorProperty = BindableProperty.Create(
nameof(ProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.FromHex("175DDC")); nameof(ProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.Default);
public static readonly BindableProperty EndingProgressColorProperty = BindableProperty.Create( public static readonly BindableProperty EndingProgressColorProperty = BindableProperty.Create(
nameof(EndingProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.FromHex("dd4b39")); nameof(EndingProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.Default);
public static readonly BindableProperty BackgroundProgressColorProperty = BindableProperty.Create( public static readonly BindableProperty BackgroundProgressColorProperty = BindableProperty.Create(
nameof(BackgroundProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.White); nameof(BackgroundProgressColor), typeof(Color), typeof(CircularProgressbarView), Color.Default);
public double Progress public double Progress
{ {

View File

@@ -1,13 +0,0 @@
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class CustomLabel : Label
{
public CustomLabel()
{
}
public int? FontWeight { get; set; }
}
}

View File

@@ -1,6 +1,4 @@
using System.Linq; using Xamarin.Forms;
using Xamarin.CommunityToolkit.Converters;
using Xamarin.Forms;
namespace Bit.App.Controls namespace Bit.App.Controls
{ {
@@ -8,13 +6,4 @@ namespace Bit.App.Controls
{ {
public string ExtraDataForLogging { get; set; } public string ExtraDataForLogging { get; set; }
} }
public class SelectionChangedEventArgsConverter : BaseNullableConverterOneWay<SelectionChangedEventArgs, object>
{
public override object? ConvertFrom(SelectionChangedEventArgs? value)
{
return value?.CurrentSelection.FirstOrDefault();
}
}
} }

View File

@@ -5,7 +5,7 @@ namespace Bit.App.Controls
public class ExtendedSlider : Slider public class ExtendedSlider : Slider
{ {
public static readonly BindableProperty ThumbBorderColorProperty = BindableProperty.Create( public static readonly BindableProperty ThumbBorderColorProperty = BindableProperty.Create(
nameof(ThumbBorderColor), typeof(Color), typeof(ExtendedSlider), Color.FromHex("b5b5b5")); nameof(ThumbBorderColor), typeof(Color), typeof(ExtendedSlider), Color.Default);
public Color ThumbBorderColor public Color ThumbBorderColor
{ {

View File

@@ -5,10 +5,10 @@ namespace Bit.App.Controls
public class ExtendedStepper : Stepper public class ExtendedStepper : Stepper
{ {
public static readonly BindableProperty StepperBackgroundColorProperty = BindableProperty.Create( public static readonly BindableProperty StepperBackgroundColorProperty = BindableProperty.Create(
nameof(StepperBackgroundColor), typeof(Color), typeof(ExtendedStepper), Color.White); nameof(StepperBackgroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
public static readonly BindableProperty StepperForegroundColorProperty = BindableProperty.Create( public static readonly BindableProperty StepperForegroundColorProperty = BindableProperty.Create(
nameof(StepperForegroundColor), typeof(Color), typeof(ExtendedStepper), Color.Black); nameof(StepperForegroundColor), typeof(Color), typeof(ExtendedStepper), Color.Default);
public Color StepperBackgroundColor public Color StepperBackgroundColor
{ {

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