diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 7f35d2cb1..c3847e761 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -7,6 +7,12 @@
"commands": [
"dotnet-format"
]
+ },
+ "cake.tool": {
+ "version": "2.2.0",
+ "commands": [
+ "dotnet-cake"
+ ]
}
}
}
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 2ff9e650c..b24500bc9 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -22,7 +22,7 @@
## Before you submit
-- [ ] I have checked for formatting errors (`dotnet tool run dotnet-format --check`) (required)
-- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
-- [ ] This change requires a **documentation update** (notify the documentation team)
-- [ ] This change has particular **deployment requirements** (notify the DevOps team)
+- Please check for formatting errors (`dotnet format --verify-no-changes`) (required)
+- Please add **unit tests** where it makes sense to do so (encouraged but not required)
+- If this change requires a **documentation update** - notify the documentation team
+- If this change has particular **deployment requirements** - notify the DevOps team
diff --git a/renovate.json b/.github/renovate.json
similarity index 100%
rename from renovate.json
rename to .github/renovate.json
diff --git a/.github/resources/export-options-app-store.plist b/.github/resources/export-options-app-store.plist
index df3ed635b..dc991f2a8 100644
--- a/.github/resources/export-options-app-store.plist
+++ b/.github/resources/export-options-app-store.plist
@@ -14,6 +14,10 @@
Dist: Extension 2021
com.8bit.bitwarden.share-extension
Dist: Share Extension 2021
+ com.8bit.bitwarden.watchkitapp
+ Dist: Bitwarden Watch App
+ com.8bit.bitwarden.watchkitapp.watchkitextension
+ Dist: Bitwarden Watch App Extension
diff --git a/.github/secrets/dist_watch_app.mobileprovision.gpg b/.github/secrets/dist_watch_app.mobileprovision.gpg
new file mode 100644
index 000000000..8981acabe
Binary files /dev/null and b/.github/secrets/dist_watch_app.mobileprovision.gpg differ
diff --git a/.github/secrets/dist_watch_app_extension.mobileprovision.gpg b/.github/secrets/dist_watch_app_extension.mobileprovision.gpg
new file mode 100644
index 000000000..57b1bf2dd
Binary files /dev/null and b/.github/secrets/dist_watch_app_extension.mobileprovision.gpg differ
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a47d79835..1087f9404 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -42,15 +42,15 @@ jobs:
id: branch-check
run: |
if [[ $(git ls-remote --heads origin rc) ]]; then
- echo "::set-output name=rc_branch_exists::1"
+ echo "rc_branch_exists=1" >> $GITHUB_OUTPUT
else
- echo "::set-output name=rc_branch_exists::0"
+ echo "rc_branch_exists=0" >> $GITHUB_OUTPUT
fi
if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then
- echo "::set-output name=hotfix_branch_exists::1"
+ echo "hotfix_branch_exists=1" >> $GITHUB_OUTPUT
else
- echo "::set-output name=hotfix_branch_exists::0"
+ echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT
fi
shell: bash
@@ -59,6 +59,10 @@ jobs:
name: Android
runs-on: windows-2022
needs: setup
+ strategy:
+ fail-fast: false
+ matrix:
+ variant: ['prod', 'qa']
steps:
- name: Setup NuGet
uses: nuget/setup-nuget@b2bc17b761a1d88cab755a776c7922eb26eefbfa # v1.0.6
@@ -67,7 +71,7 @@ jobs:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
-
+
- name: Work Around for broken Windows 2022 Runner Image
run: |
Set-Location "C:\Program Files (x86)\Microsoft Visual Studio\Installer\"
@@ -87,7 +91,6 @@ jobs:
Write-Host "components were not installed"
exit 1
}
-
- name: Print environment
run: |
nuget help | grep Version
@@ -98,7 +101,8 @@ jobs:
- name: Checkout repo
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
-
+ with:
+ fetch-depth: 0
- name: Decrypt secrets
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
@@ -109,12 +113,17 @@ jobs:
--output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output ./src/Android/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg
- gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
- --output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
shell: bash
-
+ - name: 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
run: |
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
@@ -142,26 +151,35 @@ jobs:
run: dotnet test test/Core.Test/Core.Test.csproj
- name: Build Play Store publisher
+ if: ${{ matrix.variant == 'prod' }}
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
- - name: Build for Play Store
+ - name: Setup Android build (${{ matrix.variant }})
+ run: dotnet cake build.cake --target Android --variant ${{ matrix.variant }}
+
+ - name: Build Android
run: |
$configuration = "Release";
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration"
Write-Output "########################################"
-
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
+
shell: pwsh
- - name: Sign for Play Store
+ - name: Sign Android Build
env:
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
run: |
$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 "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################"
@@ -175,9 +193,8 @@ jobs:
Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################"
- $signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab");
- $signedAabDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.aab");
-
+ $signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.aab");
+ $signedAabDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).aab");
Copy-Item $signedAabPath $signedAabDestPath
Write-Output "########################################"
@@ -193,33 +210,41 @@ jobs:
Write-Output "##### Copy Release APK to project root"
Write-Output "########################################"
- $signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk");
- $signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.apk");
+ $signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.apk");
+ $signedApkDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).apk");
Copy-Item $signedApkPath $signedApkDestPath
shell: pwsh
-
- - name: Upload Play Store .aab artifact
+ - name: Upload Prod .aab artifact
+ if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab
if-no-files-found: error
- - name: Upload Play Store .apk artifact
+ - name: Upload Prod .apk artifact
+ if: ${{ matrix.variant == 'prod' }}
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
with:
name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk
if-no-files-found: error
+ - name: Upload Other .apk artifact
+ if: ${{ matrix.variant != 'prod' }}
+ uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
+ with:
+ name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
+ path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
+ if-no-files-found: error
+
- name: Deploy to Play Store
- if: |
- (github.ref == 'refs/heads/master'
- && needs.setup.outputs.rc_branch_exists == 0
- && needs.setup.outputs.hotfix_branch_exists == 0)
- || (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
- || github.ref == 'refs/heads/hotfix-rc'
+ if: ${{ matrix.variant == 'prod' && (( 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: |
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll"
CREDS_PATH="$HOME/secrets/play_creds.json"
@@ -441,10 +466,17 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
- uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
- with:
- keyvault: "bitwarden-prod-kv"
- secrets: "appcenter-ios-token"
+ env:
+ KEYVAULT: bitwarden-prod-kv
+ SECRETS: |
+ appcenter-ios-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 "$i=$VALUE" >> $GITHUB_OUTPUT
+ done
- name: Decrypt secrets
env:
@@ -465,6 +497,12 @@ jobs:
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
--output $HOME/secrets/dist_share_extension.mobileprovision \
./.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
shell: bash
- name: Increment version
@@ -479,6 +517,9 @@ jobs:
perl -0777 -pi.bak -e 's/CFBundleVersion<\/key>\s*1<\/string>/CFBundleVersion<\/key>\n\t'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
perl -0777 -pi.bak -e 's/CFBundleVersion<\/key>\s*1<\/string>/CFBundleVersion<\/key>\n\t'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
perl -0777 -pi.bak -e 's/CFBundleVersion<\/key>\s*1<\/string>/CFBundleVersion<\/key>\n\t'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.ShareExtension/Info.plist
+ cd src/watchOS/bitwarden
+ agvtool new-version -all $BUILD_NUMBER
+ cd ../../..
shell: bash
- name: Update Entitlements
@@ -513,6 +554,8 @@ jobs:
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_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
mkdir -p "$PROFILES_DIR_PATH"
@@ -528,6 +571,28 @@ jobs:
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"
+
+ 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 "########################################"
+ cd src/watchOS
+ ls -R
+ cd ../..
shell: bash
- name: Restore packages
@@ -635,10 +700,17 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
- uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
- with:
- keyvault: "bitwarden-prod-kv"
- secrets: "crowdin-api-token"
+ env:
+ KEYVAULT: bitwarden-prod-kv
+ SECRETS: |
+ 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 "$i=$VALUE" >> $GITHUB_OUTPUT
+ done
- name: Upload Sources
uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415
@@ -695,11 +767,18 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
- uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
if: failure()
- with:
- keyvault: "bitwarden-prod-kv"
- secrets: "devops-alerts-slack-webhook-url"
+ env:
+ KEYVAULT: bitwarden-prod-kv
+ SECRETS: |
+ devops-alerts-slack-webhook-url
+ 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 "$i=$VALUE" >> $GITHUB_OUTPUT
+ done
- name: Notify Slack on failure
uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33
diff --git a/.github/workflows/crowdin-pull.yml b/.github/workflows/crowdin-pull.yml
index 90fb1f826..6dd2e3c4f 100644
--- a/.github/workflows/crowdin-pull.yml
+++ b/.github/workflows/crowdin-pull.yml
@@ -24,10 +24,10 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
- uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
+ uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
with:
- keyvault: "bitwarden-prod-kv"
- secrets: "crowdin-api-token"
+ keyvault: "bitwarden-prod-kv"
+ secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Download translations
uses: crowdin/github-action@12143a68c213f3c6d9913c9e5023224f7231face
@@ -40,10 +40,12 @@ jobs:
upload_sources: false
upload_translations: false
download_translations: true
- github_user_name: "github-actions"
- github_user_email: "<>"
+ github_user_name: "bitwarden-devops-bot"
+ github_user_email: "106330231+bitwarden-devops-bot@users.noreply.github.com"
commit_message: "Autosync the updated translations"
localization_branch_name: crowdin-auto-sync
create_pull_request: true
pull_request_title: "Autosync Crowdin Translations"
pull_request_body: "Autosync the updated translations"
+ gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
+ gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index b6a054d25..60c5398f6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,5 +1,6 @@
---
name: Release
+run-name: Release ${{ inputs.release_type }}
on:
workflow_dispatch:
@@ -51,9 +52,10 @@ jobs:
id: branch
run: |
BRANCH_NAME=$(basename ${{ github.ref }})
- echo "::set-output name=branch-name::$BRANCH_NAME"
+ echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT
- name: Create GitHub deployment
+ if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: chrnorm/deployment-action@1b599fe41a0ef1f95191e7f2eec4743f2d7dfc48
id: deployment
with:
@@ -72,7 +74,7 @@ jobs:
workflow_conclusion: success
branch: ${{ steps.branch.outputs.branch-name }}
- - name: Download all artifacts
+ - name: Dry Run - Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with:
@@ -99,7 +101,7 @@ jobs:
draft: true
- name: Update deployment status to Success
- if: ${{ success() }}
+ if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }}
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
with:
token: '${{ secrets.GITHUB_TOKEN }}'
@@ -107,7 +109,7 @@ jobs:
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
- name: Update deployment status to Failure
- if: ${{ failure() }}
+ if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }}
uses: chrnorm/deployment-status@07b3930847f65e71c9c6802ff5a402f6dfb46b86
with:
token: '${{ secrets.GITHUB_TOKEN }}'
@@ -133,7 +135,7 @@ jobs:
branch: ${{ needs.release.outputs.branch-name }}
name: com.x8bit.bitwarden-fdroid.apk
- - name: Download F-Droid .apk artifact
+ - name: Dry Run - Download F-Droid .apk artifact
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
with:
diff --git a/.github/workflows/version-auto-bump.yml b/.github/workflows/version-auto-bump.yml
index fefb1f191..2af6da884 100644
--- a/.github/workflows/version-auto-bump.yml
+++ b/.github/workflows/version-auto-bump.yml
@@ -2,39 +2,38 @@
name: Version Auto Bump
on:
- release:
- types: [published]
+ push:
+ tags:
+ - v**
jobs:
-
setup:
name: "Setup"
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
outputs:
version_number: ${{ steps.version.outputs.new-version }}
steps:
- name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
- - name: Get version to bump
+ - name: Calculate bumped version
id: version
env:
- RELEASE_TAG: ${{ github.event.release.tag }}
+ RELEASE_TAG: ${{ github.ref }}
run: |
- CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]\.)([0-9])/\1/')
- CURR_VER=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]\.)([0-9])/\2/')
- echo $CURR_VER
+ CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/')
+ CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/')
+ echo "Current Major: $CURR_MAJOR"
+ echo "Current Patch: $CURR_PATCH"
- ((CURR_VER++))
- NEW_VER=$CURR_MAJOR$CURR_VER
-
- echo $NEW_VER
-
- echo "::set-output name=new-version::$NEW_VER"
+ NEW_PATCH=$((CURR_PATCH+1))
+ NEW_VER=$CURR_MAJOR.$NEW_PATCH
+ echo "New Version: $NEW_VER"
+ echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
trigger_version_bump:
name: "Trigger version bump workflow"
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
needs:
- setup
steps:
@@ -45,13 +44,10 @@ jobs:
- 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"
+ uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
+ with:
+ keyvault: "bitwarden-prod-kv"
+ secrets: "github-pat-bitwarden-devops-bot-repo-scope"
- name: Call GitHub API to trigger workflow bump
env:
diff --git a/.github/workflows/version-bump.yml b/.github/workflows/version-bump.yml
index b3c5a58ef..9347255f7 100644
--- a/.github/workflows/version-bump.yml
+++ b/.github/workflows/version-bump.yml
@@ -16,9 +16,28 @@ jobs:
- name: Checkout Branch
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
+ - name: Login to Azure - Prod Subscription
+ uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
+ with:
+ creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
+
+ - name: Retrieve secrets
+ id: retrieve-secrets
+ uses: bitwarden/gh-actions/get-keyvault-secrets@c3b3285993151c5af47cefcb3b9134c28ab479af
+ with:
+ keyvault: "bitwarden-prod-kv"
+ secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
+
+ - name: Import GPG key
+ uses: crazy-max/ghaction-import-gpg@c8bb57c57e8df1be8c73ff3d59deab1dbc00e0d1
+ 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
- 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
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
@@ -52,23 +71,22 @@ jobs:
- name: Setup git
run: |
- git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
- git config --local user.name "github-actions[bot]"
+ git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
+ git config --local user.name "bitwarden-devops-bot"
- name: Check if version changed
id: version-changed
run: |
if [ -n "$(git status --porcelain)" ]; then
- echo "::set-output name=changes_to_commit::TRUE"
+ echo "changes_to_commit=TRUE" >> $GITHUB_OUTPUT
else
- echo "::set-output name=changes_to_commit::FALSE"
+ echo "changes_to_commit=FALSE" >> $GITHUB_OUTPUT
echo "No changes to commit!";
fi
- name: Commit files
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
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
diff --git a/.gitignore b/.gitignore
index 383caf2b4..107fad1df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -208,4 +208,130 @@ FakesAssemblies/
# Other
project.lock.json
.DS_Store
-src/App/Css
\ No newline at end of file
+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
diff --git a/README.md b/README.md
index 3a268ac0b..0e9534d08 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin
# Build/Run
-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.
+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.
# We're Hiring!
diff --git a/build.cake b/build.cake
new file mode 100644
index 000000000..f30c8404b
--- /dev/null
+++ b/build.cake
@@ -0,0 +1,346 @@
+#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;
+
+var debugScript = Argument("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 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
+}
+
+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",
+ _ => 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)
+ {
+ 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() { "$(AppIdentifierPrefix)" + buildVariant.iOSBundleId };
+ Entitlements["com.apple.security.application-groups"] = new List() { $"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!");
+}
+
+Task("UpdateiOSIcon")
+ .Does(()=>{
+ //TODO we'll implement variant icons later
+ Information($"Updating IOS App Icon");
+ });
+
+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(".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);
+ }
+ });
+#endregion iOS
+
+#region Main Tasks
+Task("Android")
+ //.IsDependentOn("UpdateAndroidAppIcon")
+ .IsDependentOn("UpdateAndroidManifest")
+ .IsDependentOn("UpdateAndroidCodeFiles")
+ .Does(()=>
+ {
+ Information("Android app updated");
+ });
+
+Task("iOS")
+ //.IsDependentOn("UpdateiOSIcon")
+ .IsDependentOn("UpdateiOSPlist")
+ .IsDependentOn("UpdateiOSAutofillPlist")
+ .IsDependentOn("UpdateiOSExtensionPlist")
+ .IsDependentOn("UpdateiOSShareExtensionPlist")
+ .IsDependentOn("UpdateiOSCodeFiles")
+ .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= Script debug mode.
+";
+ Information(usage);
+ });
+#endregion Main Tasks
+
+RunTarget(target);
\ No newline at end of file
diff --git a/src/Android/Accessibility/AccessibilityHelpers.cs b/src/Android/Accessibility/AccessibilityHelpers.cs
index 81d7fa65f..2f0cd4ce6 100644
--- a/src/Android/Accessibility/AccessibilityHelpers.cs
+++ b/src/Android/Accessibility/AccessibilityHelpers.cs
@@ -33,6 +33,7 @@ namespace Bit.Droid.Accessibility
// - Resources/xml/autofillservice.xml
new Browser("alook.browser", "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.android.browser", "url"),
new Browser("com.android.chrome", "url_bar"),
@@ -66,6 +67,7 @@ namespace Bit.Droid.Accessibility
new Browser("com.mmbox.xbrowser", "search_box"),
new Browser("com.mycompany.app.soulbrowser", "edit_text"),
new Browser("com.naver.whale", "url_bar"),
+ new Browser("com.neeva.app", "full_url_text_view"),
new Browser("com.opera.browser", "url_field"),
new Browser("com.opera.browser.beta", "url_field"),
new Browser("com.opera.gx", "addressbarEdit"),
@@ -90,6 +92,7 @@ namespace Bit.Droid.Accessibility
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
new Browser("mark.via", "am,an"),
new Browser("mark.via.gp", "as"),
+ new Browser("net.dezor.browser", "url_bar"),
new Browser("net.slions.fulguris.full.download", "search"),
new Browser("net.slions.fulguris.full.download.debug", "search"),
new Browser("net.slions.fulguris.full.playstore", "search"),
@@ -367,7 +370,7 @@ namespace Bit.Droid.Accessibility
public static string GetUri(AccessibilityNodeInfo root)
{
- var uri = string.Concat(Constants.AndroidAppProtocol, root.PackageName);
+ var uri = string.Concat(Core.Constants.AndroidAppProtocol, root.PackageName);
if (SupportedBrowsers.ContainsKey(root.PackageName))
{
var browser = SupportedBrowsers[root.PackageName];
diff --git a/src/Android/Accessibility/AccessibilityService.cs b/src/Android/Accessibility/AccessibilityService.cs
index 1345a5cef..d57ae56a9 100644
--- a/src/Android/Accessibility/AccessibilityService.cs
+++ b/src/Android/Accessibility/AccessibilityService.cs
@@ -15,7 +15,7 @@ using Bit.Core.Utilities;
namespace Bit.Droid.Accessibility
{
- [Service(Permission = Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden")]
+ [Service(Permission = Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden", Exported = true)]
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
[Register("com.x8bit.bitwarden.Accessibility.AccessibilityService")]
diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj
index f85cd5c15..8d01e1091 100644
--- a/src/Android/Android.csproj
+++ b/src/Android/Android.csproj
@@ -15,7 +15,7 @@
Properties\AndroidManifest.xml
Resources
Assets
- v11.0
+ v13.0
Xamarin.Android.Net.AndroidClientHandler
@@ -75,24 +75,24 @@
2.1.0.4
- 1.8.10
+ 1.9.0
-
-
-
-
-
-
+
+
+
+
+
+
1.7.3
- 122.0.0
+ 123.0.8
-
-
+
+
- 117.0.1
+ 118.0.1.2
@@ -103,8 +103,10 @@
+
+
@@ -152,6 +154,12 @@
+
+
+
+
+
+
@@ -176,6 +184,7 @@
+
@@ -213,6 +222,13 @@
+
+
+
+
+
+
+
@@ -280,6 +296,8 @@
+
+
\ No newline at end of file
diff --git a/src/Android/Assets/bwi-font.ttf b/src/Android/Assets/bwi-font.ttf
index 358fca186..7c7afd4cd 100644
Binary files a/src/Android/Assets/bwi-font.ttf and b/src/Android/Assets/bwi-font.ttf differ
diff --git a/src/Android/Autofill/AutofillConstants.cs b/src/Android/Autofill/AutofillConstants.cs
new file mode 100644
index 000000000..9bb1782f7
--- /dev/null
+++ b/src/Android/Autofill/AutofillConstants.cs
@@ -0,0 +1,10 @@
+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";
+ }
+}
diff --git a/src/Android/Autofill/AutofillExternalSelectionActivity.cs b/src/Android/Autofill/AutofillExternalSelectionActivity.cs
new file mode 100644
index 000000000..c75d150be
--- /dev/null
+++ b/src/Android/Autofill/AutofillExternalSelectionActivity.cs
@@ -0,0 +1,42 @@
+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();
+ var cipher = await cipherService.GetAsync(cipherId);
+ var decCipher = await cipher.DecryptAsync();
+
+ var autofillHandler = ServiceContainer.Resolve();
+ autofillHandler.Autofill(decCipher);
+ }
+ }
+}
diff --git a/src/Android/Autofill/AutofillHelpers.cs b/src/Android/Autofill/AutofillHelpers.cs
index 8ec3416bd..924836941 100644
--- a/src/Android/Autofill/AutofillHelpers.cs
+++ b/src/Android/Autofill/AutofillHelpers.cs
@@ -19,6 +19,7 @@ using AndroidX.AutoFill.Inline;
using AndroidX.AutoFill.Inline.V1;
using Bit.Core.Abstractions;
using SaveFlags = Android.Service.Autofill.SaveFlags;
+using Bit.Droid.Utilities;
namespace Bit.Droid.Autofill
{
@@ -53,6 +54,7 @@ namespace Bit.Droid.Autofill
{
"alook.browser",
"alook.browser.google",
+ "app.vanadium.browser",
"com.amazon.cloud9",
"com.android.browser",
"com.android.chrome",
@@ -85,6 +87,7 @@ namespace Bit.Droid.Autofill
"com.mmbox.xbrowser",
"com.mycompany.app.soulbrowser",
"com.naver.whale",
+ "com.neeva.app",
"com.opera.browser",
"com.opera.browser.beta",
"com.opera.gx",
@@ -108,6 +111,7 @@ namespace Bit.Droid.Autofill
"io.github.forkmaintainers.iceraven",
"mark.via",
"mark.via.gp",
+ "net.dezor.browser",
"net.slions.fulguris.full.download",
"net.slions.fulguris.full.download.debug",
"net.slions.fulguris.full.playstore",
@@ -206,7 +210,7 @@ namespace Bit.Droid.Autofill
}
}
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i],
- inlinePresentationSpec);
+ true, inlinePresentationSpec);
if (dataset != null)
{
responseBuilder.AddDataset(dataset);
@@ -220,7 +224,7 @@ namespace Bit.Droid.Autofill
}
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem,
- InlinePresentationSpec inlinePresentationSpec = null)
+ bool includeAuthIntent, InlinePresentationSpec inlinePresentationSpec = null)
{
var overlayPresentation = BuildOverlayPresentation(
filledItem.Name,
@@ -241,6 +245,15 @@ namespace Bit.Droid.Autofill
{
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))
{
return datasetBuilder.Build();
@@ -252,26 +265,26 @@ namespace Bit.Droid.Autofill
IList inlinePresentationSpecs = null)
{
var intent = new Intent(context, typeof(MainActivity));
- intent.PutExtra("autofillFramework", true);
+ intent.PutExtra(AutofillConstants.AutofillFramework, true);
if (fields.FillableForLogin)
{
- intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Login);
+ intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Login);
}
else if (fields.FillableForCard)
{
- intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Card);
+ intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Card);
}
else if (fields.FillableForIdentity)
{
- intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Identity);
+ intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Identity);
}
else
{
return null;
}
- intent.PutExtra("autofillFrameworkUri", uri);
+ intent.PutExtra(AutofillConstants.AutofillFrameworkUri, uri);
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
- PendingIntentFlags.CancelCurrent);
+ AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
var overlayPresentation = BuildOverlayPresentation(
AppResources.AutofillWithBitwarden,
@@ -324,7 +337,7 @@ namespace Bit.Droid.Autofill
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the
// "my vault" presentation) so we're including an empty one here
pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
- PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent);
+ AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, true));
}
var slice = CreateInlinePresentationSlice(
inlinePresentationSpec,
diff --git a/src/Android/Autofill/AutofillService.cs b/src/Android/Autofill/AutofillService.cs
index 07171eb9e..3becfe4ac 100644
--- a/src/Android/Autofill/AutofillService.cs
+++ b/src/Android/Autofill/AutofillService.cs
@@ -15,7 +15,7 @@ using Bit.Core.Utilities;
namespace Bit.Droid.Autofill
{
- [Service(Permission = Manifest.Permission.BindAutofillService, Label = "Bitwarden")]
+ [Service(Permission = Manifest.Permission.BindAutofillService, Label = "Bitwarden", Exported = true)]
[IntentFilter(new string[] { "android.service.autofill.AutofillService" })]
[MetaData("android.autofill", Resource = "@xml/autofillservice")]
[Register("com.x8bit.bitwarden.Autofill.AutofillService")]
@@ -134,7 +134,7 @@ namespace Bit.Droid.Autofill
{
case CipherType.Login:
intent.PutExtra("autofillFrameworkName", parser.Uri
- .Replace(Constants.AndroidAppProtocol, string.Empty)
+ .Replace(Core.Constants.AndroidAppProtocol, string.Empty)
.Replace("https://", string.Empty)
.Replace("http://", string.Empty));
intent.PutExtra("autofillFrameworkUri", parser.Uri);
diff --git a/src/Android/Autofill/FilledItem.cs b/src/Android/Autofill/FilledItem.cs
index 3964f37bd..46b0436fc 100644
--- a/src/Android/Autofill/FilledItem.cs
+++ b/src/Android/Autofill/FilledItem.cs
@@ -23,6 +23,7 @@ namespace Bit.Droid.Autofill
public FilledItem(CipherView cipher)
{
+ Id = cipher.Id;
Name = cipher.Name;
Type = cipher.Type;
Subtitle = cipher.SubTitle;
@@ -55,6 +56,7 @@ namespace Bit.Droid.Autofill
}
}
+ public string Id { get; set; }
public string Name { get; set; }
public string Subtitle { get; set; } = string.Empty;
public int Icon { get; set; } = Resource.Drawable.login;
diff --git a/src/Android/Autofill/Parser.cs b/src/Android/Autofill/Parser.cs
index 45a2fdb0b..4bc2ebca5 100644
--- a/src/Android/Autofill/Parser.cs
+++ b/src/Android/Autofill/Parser.cs
@@ -48,7 +48,7 @@ namespace Bit.Droid.Autofill
}
else
{
- _uri = string.Concat(Constants.AndroidAppProtocol, PackageName);
+ _uri = string.Concat(Core.Constants.AndroidAppProtocol, PackageName);
}
return _uri;
}
diff --git a/src/Android/Constants.cs b/src/Android/Constants.cs
new file mode 100644
index 000000000..398bfb708
--- /dev/null
+++ b/src/Android/Constants.cs
@@ -0,0 +1,7 @@
+namespace Bit.Droid
+{
+ public static class Constants
+ {
+ public const string PACKAGE_NAME = "com.x8bit.bitwarden";
+ }
+}
diff --git a/src/Android/Effects/RemoveFontPaddingEffect.cs b/src/Android/Effects/RemoveFontPaddingEffect.cs
new file mode 100644
index 000000000..1f7cf1297
--- /dev/null
+++ b/src/Android/Effects/RemoveFontPaddingEffect.cs
@@ -0,0 +1,23 @@
+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()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Android/MainActivity.cs b/src/Android/MainActivity.cs
index 39fdfa6f1..2e25bc1cc 100644
--- a/src/Android/MainActivity.cs
+++ b/src/Android/MainActivity.cs
@@ -5,20 +5,27 @@ using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Content.PM;
+using Android.Content.Res;
using Android.Nfc;
using Android.OS;
using Android.Runtime;
-using AndroidX.Core.Content;
+using Android.Views;
using Bit.App.Abstractions;
using Bit.App.Models;
+using Bit.App.Resources;
using Bit.App.Utilities;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Utilities;
+using Bit.Droid.Autofill;
using Bit.Droid.Receivers;
using Bit.Droid.Utilities;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Xamarin.Essentials;
using ZXing.Net.Mobile.Android;
+using FileProvider = AndroidX.Core.Content.FileProvider;
namespace Bit.Droid
{
@@ -30,11 +37,14 @@ namespace Bit.Droid
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
private IDeviceActionService _deviceActionService;
+ private IFileService _fileService;
private IMessagingService _messagingService;
private IBroadcasterService _broadcasterService;
private IStateService _stateService;
private IAppIdService _appIdService;
private IEventService _eventService;
+ private IPushNotificationListenerService _pushNotificationListenerService;
+ private ILogger _logger;
private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions;
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
@@ -45,17 +55,20 @@ namespace Bit.Droid
{
var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver));
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
- PendingIntentFlags.UpdateCurrent);
+ AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false));
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
StrictMode.SetThreadPolicy(policy);
_deviceActionService = ServiceContainer.Resolve("deviceActionService");
+ _fileService = ServiceContainer.Resolve();
_messagingService = ServiceContainer.Resolve("messagingService");
_broadcasterService = ServiceContainer.Resolve("broadcasterService");
_stateService = ServiceContainer.Resolve("stateService");
_appIdService = ServiceContainer.Resolve("appIdService");
_eventService = ServiceContainer.Resolve("eventService");
+ _pushNotificationListenerService = ServiceContainer.Resolve();
+ _logger = ServiceContainer.Resolve("logger");
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
@@ -70,7 +83,7 @@ namespace Bit.Droid
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
});
- ServiceContainer.Resolve("logger").InitAsync();
+ _logger.InitAsync();
var toplayout = Window?.DecorView?.RootView;
if (toplayout != null)
@@ -81,8 +94,9 @@ namespace Bit.Droid
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
Xamarin.Forms.Forms.Init(this, savedInstanceState);
_appOptions = GetOptions();
+ CreateNotificationChannel();
LoadApplication(new App.App(_appOptions));
-
+ DisableAndroidFontScale();
_broadcasterService.Subscribe(_activityKey, (message) =>
{
@@ -138,6 +152,15 @@ namespace Bit.Droid
AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this)
.GetAwaiter()
.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(notificationDataJson)).FireAndForget();
+ }
+ }
}
protected override void OnNewIntent(Intent intent)
@@ -191,13 +214,13 @@ namespace Bit.Droid
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions,
[GeneratedEnum] Permission[] grantResults)
{
- if (requestCode == Constants.SelectFilePermissionRequestCode)
+ if (requestCode == Core.Constants.SelectFilePermissionRequestCode)
{
if (grantResults.Any(r => r != Permission.Granted))
{
_messagingService.Send("selectFileCameraPermissionDenied");
}
- await _deviceActionService.SelectFileAsync();
+ await _fileService.SelectFileAsync();
}
else
{
@@ -210,7 +233,7 @@ namespace Bit.Droid
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if (resultCode == Result.Ok &&
- (requestCode == Constants.SelectFileRequestCode || requestCode == Constants.SaveFileRequestCode))
+ (requestCode == Core.Constants.SelectFileRequestCode || requestCode == Core.Constants.SaveFileRequestCode))
{
Android.Net.Uri uri = null;
string fileName = null;
@@ -232,7 +255,7 @@ namespace Bit.Droid
return;
}
- if (requestCode == Constants.SaveFileRequestCode)
+ if (requestCode == Core.Constants.SaveFileRequestCode)
{
_messagingService.Send("selectSaveFileResult",
new Tuple(uri.ToString(), fileName));
@@ -273,7 +296,7 @@ namespace Bit.Droid
{
var intent = new Intent(this, Class);
intent.AddFlags(ActivityFlags.SingleTop);
- var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
+ var pendingIntent = PendingIntent.GetActivity(this, 0, intent, AndroidHelpers.AddPendingIntentMutabilityFlag(0, true));
// register for all NDEF tags starting with http och https
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
ndef.AddDataScheme("http");
@@ -300,13 +323,13 @@ namespace Bit.Droid
{
var options = new AppOptions
{
- Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
+ Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra(AutofillConstants.AutofillFrameworkUri),
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
- FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false),
+ FromAutofillFramework = Intent.GetBooleanExtra(AutofillConstants.AutofillFramework, false),
CreateSend = GetCreateSendRequest(Intent)
};
- var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
+ var fillType = Intent.GetIntExtra(AutofillConstants.AutofillFrameworkFillType, 0);
if (fillType > 0)
{
options.FillType = (CipherType)fillType;
@@ -401,5 +424,38 @@ namespace Bit.Droid
alarmManager.Cancel(_eventUploadPendingIntent);
await _eventService.UploadEventsAsync();
}
+
+ private void CreateNotificationChannel()
+ {
+#if !FDROID
+ if (Build.VERSION.SdkInt < BuildVersionCodes.O)
+ {
+ // Notification channels are new in API 26 (and not a part of the
+ // support library). There is no need to create a notification
+ // channel on older versions of Android.
+ return;
+ }
+
+ var channel = new NotificationChannel(Core.Constants.AndroidNotificationChannelId, AppResources.AllNotifications, NotificationImportance.Default);
+ if(GetSystemService(NotificationService) is NotificationManager notificationManager)
+ {
+ notificationManager.CreateNotificationChannel(channel);
+ }
+#endif
+ }
+
+ private void DisableAndroidFontScale()
+ {
+ try
+ {
+ //As we are using NamedSizes the xamarin will change the font size. So we are disabling the Android scaling.
+ Resources.Configuration.FontScale = 1f;
+ BaseContext.Resources.DisplayMetrics.ScaledDensity = Resources.Configuration.FontScale * (float)DeviceDisplay.MainDisplayInfo.Density;
+ }
+ catch (Exception e)
+ {
+ _logger.Exception(e);
+ }
+ }
}
}
diff --git a/src/Android/MainApplication.cs b/src/Android/MainApplication.cs
index bea0569ff..db042bcf7 100644
--- a/src/Android/MainApplication.cs
+++ b/src/Android/MainApplication.cs
@@ -45,9 +45,17 @@ namespace Bit.Droid
if (ServiceContainer.RegisteredServices.Count == 0)
{
RegisterLocalServices();
+
var deviceActionService = ServiceContainer.Resolve("deviceActionService");
- ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
- Constants.AndroidAllClearCipherCacheKeys);
+ ServiceContainer.Init(deviceActionService.DeviceUserAgent, Core.Constants.ClearCiphersCacheKey,
+ Core.Constants.AndroidAllClearCipherCacheKeys);
+
+ ServiceContainer.Register(new WatchDeviceService(ServiceContainer.Resolve(),
+ ServiceContainer.Resolve(),
+ ServiceContainer.Resolve(),
+ ServiceContainer.Resolve()));
+
+ InitializeAppSetup();
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
@@ -71,7 +79,9 @@ namespace Bit.Droid
ServiceContainer.Resolve("stateService"),
ServiceContainer.Resolve("platformUtilsService"),
ServiceContainer.Resolve("authService"),
- ServiceContainer.Resolve("logger"));
+ ServiceContainer.Resolve("logger"),
+ ServiceContainer.Resolve("messagingService"),
+ ServiceContainer.Resolve());
ServiceContainer.Register("accountsManager", accountsManager);
}
#if !FDROID
@@ -137,10 +147,12 @@ namespace Bit.Droid
var stateMigrationService =
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
var clipboardService = new ClipboardService(stateService);
- var deviceActionService = new DeviceActionService(clipboardService, stateService, messagingService,
- broadcasterService, () => ServiceContainer.Resolve("eventService"));
+ var deviceActionService = new DeviceActionService(stateService, messagingService);
+ var fileService = new FileService(stateService, broadcasterService);
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
messagingService, broadcasterService);
+ var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
+ platformUtilsService, new LazyResolve());
var biometricService = new BiometricService();
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
@@ -157,6 +169,8 @@ namespace Bit.Droid
ServiceContainer.Register("stateMigrationService", stateMigrationService);
ServiceContainer.Register("clipboardService", clipboardService);
ServiceContainer.Register("deviceActionService", deviceActionService);
+ ServiceContainer.Register(fileService);
+ ServiceContainer.Register(autofillHandler);
ServiceContainer.Register("platformUtilsService", platformUtilsService);
ServiceContainer.Register("biometricService", biometricService);
ServiceContainer.Register("cryptoFunctionService", cryptoFunctionService);
@@ -193,5 +207,12 @@ namespace Bit.Droid
{
await ServiceContainer.Resolve("environmentService").SetUrlsFromStorageAsync();
}
+
+ private void InitializeAppSetup()
+ {
+ var appSetup = new AppSetup();
+ appSetup.InitializeServicesLastChance();
+ ServiceContainer.Register("appSetup", appSetup);
+ }
}
}
diff --git a/src/Android/Properties/AndroidManifest.xml b/src/Android/Properties/AndroidManifest.xml
index 6dd07923a..da8e1ddb3 100644
--- a/src/Android/Properties/AndroidManifest.xml
+++ b/src/Android/Properties/AndroidManifest.xml
@@ -1,6 +1,6 @@
-
-
+
+
@@ -40,10 +40,4 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Android/Push/FirebaseMessagingService.cs b/src/Android/Push/FirebaseMessagingService.cs
index 676390aef..887c8ac44 100644
--- a/src/Android/Push/FirebaseMessagingService.cs
+++ b/src/Android/Push/FirebaseMessagingService.cs
@@ -1,7 +1,9 @@
#if !FDROID
+using System;
using Android.App;
using Bit.App.Abstractions;
using Bit.Core.Abstractions;
+using Bit.Core.Services;
using Bit.Core.Utilities;
using Firebase.Messaging;
using Newtonsoft.Json;
@@ -16,34 +18,41 @@ namespace Bit.Droid.Push
{
public async override void OnNewToken(string token)
{
- var stateService = ServiceContainer.Resolve("stateService");
- var pushNotificationService = ServiceContainer.Resolve("pushNotificationService");
+ try {
+ var stateService = ServiceContainer.Resolve("stateService");
+ var pushNotificationService = ServiceContainer.Resolve("pushNotificationService");
- await stateService.SetPushRegisteredTokenAsync(token);
- await pushNotificationService.RegisterAsync();
+ await stateService.SetPushRegisteredTokenAsync(token);
+ await pushNotificationService.RegisterAsync();
+ }
+ catch (Exception ex)
+ {
+ Logger.Instance.Exception(ex);
+ }
}
public async override void OnMessageReceived(RemoteMessage message)
{
- if (message?.Data == null)
- {
- return;
- }
- var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
- if (data == null)
- {
- return;
- }
try
{
+ if (message?.Data == null)
+ {
+ return;
+ }
+ var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
+ if (data == null)
+ {
+ return;
+ }
+
var obj = JObject.Parse(data);
var listener = ServiceContainer.Resolve(
"pushNotificationListenerService");
await listener.OnMessageAsync(obj, Device.Android);
}
- catch (JsonReaderException ex)
+ catch (Exception ex)
{
- System.Diagnostics.Debug.WriteLine(ex.ToString());
+ Logger.Instance.Exception(ex);
}
}
}
diff --git a/src/Android/Receivers/ClearClipboardAlarmReceiver.cs b/src/Android/Receivers/ClearClipboardAlarmReceiver.cs
index ff1566134..6e57cedb7 100644
--- a/src/Android/Receivers/ClearClipboardAlarmReceiver.cs
+++ b/src/Android/Receivers/ClearClipboardAlarmReceiver.cs
@@ -1,4 +1,5 @@
using Android.Content;
+using Android.OS;
namespace Bit.Droid.Receivers
{
@@ -8,7 +9,17 @@ namespace Bit.Droid.Receivers
public override void OnReceive(Context context, Intent intent)
{
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
- clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
+ if (clipboardManager == null)
+ {
+ 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();
}
}
}
diff --git a/src/Android/Receivers/NotificationDismissReceiver.cs b/src/Android/Receivers/NotificationDismissReceiver.cs
new file mode 100644
index 000000000..43f69ea62
--- /dev/null
+++ b/src/Android/Receivers/NotificationDismissReceiver.cs
@@ -0,0 +1,41 @@
+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 _pushNotificationListenerService = new LazyResolve();
+ private readonly LazyResolve _logger = new LazyResolve();
+
+ 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(notificationDataJson)).FireAndForget();
+ }
+ }
+ }
+ catch (System.Exception ex)
+ {
+ _logger.Value.Exception(ex);
+ }
+ }
+ }
+}
+
diff --git a/src/Android/Renderers/CustomSwitchRenderer.cs b/src/Android/Renderers/CustomSwitchRenderer.cs
index 00caa6290..f937b6e77 100644
--- a/src/Android/Renderers/CustomSwitchRenderer.cs
+++ b/src/Android/Renderers/CustomSwitchRenderer.cs
@@ -44,6 +44,7 @@ namespace Bit.Droid.Renderers
}
if (Control != null)
{
+ Control.SetHintTextColor(ThemeHelpers.MutedColor);
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null);
if (t is GradientDrawable thumb)
{
diff --git a/src/Android/Resources/drawable-night-v26/splash_screen_round.xml b/src/Android/Resources/drawable-night-v26/splash_screen_round.xml
new file mode 100644
index 000000000..dc4c7209e
--- /dev/null
+++ b/src/Android/Resources/drawable-night-v26/splash_screen_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Resources/drawable-v26/splash_screen_round.xml b/src/Android/Resources/drawable-v26/splash_screen_round.xml
new file mode 100644
index 000000000..602f055dd
--- /dev/null
+++ b/src/Android/Resources/drawable-v26/splash_screen_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Android/Resources/drawable/ic_launcher_monochrome.xml b/src/Android/Resources/drawable/ic_launcher_monochrome.xml
new file mode 100644
index 000000000..5a54380d9
--- /dev/null
+++ b/src/Android/Resources/drawable/ic_launcher_monochrome.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/src/Android/Resources/drawable/ic_notification.xml b/src/Android/Resources/drawable/ic_notification.xml
new file mode 100644
index 000000000..ed92d0ce8
--- /dev/null
+++ b/src/Android/Resources/drawable/ic_notification.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/src/Android/Resources/drawable/logo_rounded.xml b/src/Android/Resources/drawable/logo_rounded.xml
new file mode 100644
index 000000000..860d4c963
--- /dev/null
+++ b/src/Android/Resources/drawable/logo_rounded.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/src/Android/Resources/mipmap-anydpi-v26/ic_launcher.xml b/src/Android/Resources/mipmap-anydpi-v26/ic_launcher.xml
index ef49c9917..1084c2408 100644
--- a/src/Android/Resources/mipmap-anydpi-v26/ic_launcher.xml
+++ b/src/Android/Resources/mipmap-anydpi-v26/ic_launcher.xml
@@ -2,5 +2,5 @@
-
+
\ No newline at end of file
diff --git a/src/Android/Resources/mipmap-anydpi-v26/ic_launcher_round.xml b/src/Android/Resources/mipmap-anydpi-v26/ic_launcher_round.xml
index ef49c9917..1084c2408 100644
--- a/src/Android/Resources/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/src/Android/Resources/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -2,5 +2,5 @@
-
+
\ No newline at end of file
diff --git a/src/Android/Resources/values-night/styles.xml b/src/Android/Resources/values-night/styles.xml
index 7e5a4074e..edfaaa455 100644
--- a/src/Android/Resources/values-night/styles.xml
+++ b/src/Android/Resources/values-night/styles.xml
@@ -4,6 +4,7 @@
-
+
+
+
+
diff --git a/src/App/Styles/Black.xaml b/src/App/Styles/Black.xaml
index 7c64d5ea3..b2027f114 100644
--- a/src/App/Styles/Black.xaml
+++ b/src/App/Styles/Black.xaml
@@ -71,6 +71,6 @@
#ffffff
#52bdfb
-
+ #F08DC7
#80BDFF
diff --git a/src/App/Styles/Dark.xaml b/src/App/Styles/Dark.xaml
index 778a2b5a1..570e9ca2e 100644
--- a/src/App/Styles/Dark.xaml
+++ b/src/App/Styles/Dark.xaml
@@ -71,6 +71,6 @@
#ffffff
#52bdfb
-
+ #F08DC7
#80BDFF
diff --git a/src/App/Styles/Light.xaml b/src/App/Styles/Light.xaml
index fef760bf0..59a424953 100644
--- a/src/App/Styles/Light.xaml
+++ b/src/App/Styles/Light.xaml
@@ -71,6 +71,6 @@
#ffffff
#175DDC
-
+ #C01176
#80BDFF
diff --git a/src/App/Styles/Nord.xaml b/src/App/Styles/Nord.xaml
index 4b83815e1..d4690c2de 100644
--- a/src/App/Styles/Nord.xaml
+++ b/src/App/Styles/Nord.xaml
@@ -71,6 +71,6 @@
#e5e9f0
#81a1c1
-
+ #F08DC7
#80BDFF
diff --git a/src/App/Styles/SolarizedDark.xaml b/src/App/Styles/SolarizedDark.xaml
new file mode 100644
index 000000000..e8402a0d2
--- /dev/null
+++ b/src/App/Styles/SolarizedDark.xaml
@@ -0,0 +1,76 @@
+
+
+ #eee8d5
+ #859900
+ #dc322f
+ #859900
+ #859900
+ #b58900
+ #839496
+ #2aa198
+ #b58900
+ #93a1a1
+ #657b83
+ #cb4b16
+
+ #002b36
+ #073642
+ #073642
+ #839496
+ #073642
+ #859900
+
+ #073642
+ #859900
+
+ #eee8d5
+ #eee8d5
+ #657b83
+
+ #073642
+ #073642
+ #073642
+ #859900
+ #073642
+
+ #eee8d5
+ #073642
+ #859900
+ #073642
+
+ #859900
+ #eee8d5
+
+ #657b83
+ #eee8d5
+
+ #859900
+ #667500
+ #4d541c
+ #e5e9f0
+ #ACB5C5
+
+ #657b83
+ #3A4251
+ #454951
+ #073642
+ #eee8d5
+ #aaaaaa
+ #99eee8d5
+
+ #859900
+ #859900
+
+ #073642
+ #eee8d5
+ #859900
+
+ #073642
+ #eee8d5
+
+ #859900
+ #F08DC7
+ #80BDFF
+
diff --git a/src/App/Styles/SolarizedDark.xaml.cs b/src/App/Styles/SolarizedDark.xaml.cs
new file mode 100644
index 000000000..a8dc19768
--- /dev/null
+++ b/src/App/Styles/SolarizedDark.xaml.cs
@@ -0,0 +1,12 @@
+using Xamarin.Forms;
+
+namespace Bit.App.Styles
+{
+ public partial class SolarizedDark : ResourceDictionary, IThemeResourceDictionary
+ {
+ public SolarizedDark()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/src/App/Styles/iOS.xaml b/src/App/Styles/iOS.xaml
index f6bd66a4c..b6041bcce 100644
--- a/src/App/Styles/iOS.xaml
+++ b/src/App/Styles/iOS.xaml
@@ -93,6 +93,7 @@
Value="{DynamicResource StepperForegroundColor}" />