├── testCommon
├── .gitignore
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── version.properties
├── images
├── welcome.png
├── settings.png
├── notification_list.png
├── pairing_success.png
├── scan_pairing_code.png
└── offer_taken_details.png
├── app
├── src
│ ├── main
│ │ ├── ic_launcher-web.png
│ │ ├── res
│ │ │ ├── drawable
│ │ │ │ ├── bisq_mark.png
│ │ │ │ ├── checkmark.png
│ │ │ │ ├── playstore_icon.png
│ │ │ │ ├── ic_delete_white_24.xml
│ │ │ │ ├── ic_debug_24.xml
│ │ │ │ ├── ic_theme_white_24.xml
│ │ │ │ ├── ic_restart_white_24.xml
│ │ │ │ ├── ic_link_white_24.xml
│ │ │ │ ├── ic_info_white_24.xml
│ │ │ │ ├── ic_stat_notifications.xml
│ │ │ │ ├── ic_qr_code_scanner_white_24.xml
│ │ │ │ ├── circular_progressbar.xml
│ │ │ │ └── ic_bisq_mark.xml
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_foreground.png
│ │ │ ├── values
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── theme_res.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ └── ic_launcher.xml
│ │ │ ├── menu
│ │ │ │ └── menu.xml
│ │ │ ├── values-night
│ │ │ │ └── colors.xml
│ │ │ ├── xml
│ │ │ │ └── settings.xml
│ │ │ └── layout
│ │ │ │ ├── activity_settings.xml
│ │ │ │ ├── activity_notification_table.xml
│ │ │ │ ├── activity_pairing_send.xml
│ │ │ │ ├── notification_cell.xml
│ │ │ │ └── activity_pairing_success.xml
│ │ ├── assets
│ │ │ └── Font Awesome 5 Free-Solid-900.otf
│ │ ├── java
│ │ │ └── bisq
│ │ │ │ └── android
│ │ │ │ ├── database
│ │ │ │ ├── DebugLogLevel.kt
│ │ │ │ ├── DebugLog.kt
│ │ │ │ ├── DebugLogDao.kt
│ │ │ │ ├── DebugLogRepository.kt
│ │ │ │ ├── DebugLogDatabase.kt
│ │ │ │ ├── NotificationDatabase.kt
│ │ │ │ ├── BisqNotificationDao.kt
│ │ │ │ ├── BisqNotification.kt
│ │ │ │ └── NotificationRepository.kt
│ │ │ │ ├── Constants.kt
│ │ │ │ ├── model
│ │ │ │ ├── DeviceStatus.kt
│ │ │ │ ├── NotificationType.kt
│ │ │ │ └── NotificationMessage.kt
│ │ │ │ ├── ui
│ │ │ │ ├── settings
│ │ │ │ │ └── SettingsActivity.kt
│ │ │ │ ├── UnpairedBaseActivity.kt
│ │ │ │ ├── debug
│ │ │ │ │ ├── DebugViewModel.kt
│ │ │ │ │ └── DebugActivity.kt
│ │ │ │ ├── UiUtil.kt
│ │ │ │ ├── PairedBaseActivity.kt
│ │ │ │ ├── notification
│ │ │ │ │ ├── NotificationViewModel.kt
│ │ │ │ │ └── NotificationDetailActivity.kt
│ │ │ │ ├── pairing
│ │ │ │ │ ├── PairingSendActivity.kt
│ │ │ │ │ ├── PairingScanActivity.kt
│ │ │ │ │ ├── RequestNotificationPermissionActivity.kt
│ │ │ │ │ └── PairingSuccessActivity.kt
│ │ │ │ ├── ThemeProvider.kt
│ │ │ │ └── DialogBuilder.kt
│ │ │ │ ├── util
│ │ │ │ ├── DateUtil.kt
│ │ │ │ ├── MaskingUtil.kt
│ │ │ │ └── QrUtil.kt
│ │ │ │ ├── ext
│ │ │ │ ├── BroadcastReceiverExt.kt
│ │ │ │ └── StringExt.kt
│ │ │ │ ├── services
│ │ │ │ ├── NotificationReceiver.kt
│ │ │ │ ├── IntentReceiver.kt
│ │ │ │ └── NotificationHandler.kt
│ │ │ │ ├── Logging.kt
│ │ │ │ └── Application.kt
│ │ └── AndroidManifest.xml
│ ├── debug
│ │ └── AndroidManifest.xml
│ ├── androidTest
│ │ └── java
│ │ │ └── bisq
│ │ │ └── android
│ │ │ ├── screens
│ │ │ ├── elements
│ │ │ │ ├── ClickableElement.kt
│ │ │ │ ├── ElementById.kt
│ │ │ │ ├── ElementByText.kt
│ │ │ │ ├── ButtonElement.kt
│ │ │ │ ├── SelectionElement.kt
│ │ │ │ ├── PreferenceElement.kt
│ │ │ │ ├── MenuItemElement.kt
│ │ │ │ ├── Element.kt
│ │ │ │ └── TextElement.kt
│ │ │ ├── PairingScanScreen.kt
│ │ │ ├── Screen.kt
│ │ │ ├── dialogs
│ │ │ │ ├── PromptDialog.kt
│ │ │ │ ├── ChoicePromptDialog.kt
│ │ │ │ ├── ThemePromptDialog.kt
│ │ │ │ ├── Dialog.kt
│ │ │ │ └── PermissionPrompt.kt
│ │ │ ├── PairingSendScreen.kt
│ │ │ ├── PairingSuccessScreen.kt
│ │ │ ├── RequestNotificationPermissionScreen.kt
│ │ │ ├── NotificationDetailScreen.kt
│ │ │ ├── WelcomeScreen.kt
│ │ │ ├── NotificationTableScreen.kt
│ │ │ └── SettingsScreen.kt
│ │ │ ├── tests
│ │ │ ├── PairingScanTest.kt
│ │ │ ├── PairingSendTest.kt
│ │ │ ├── NotificationDetailTest.kt
│ │ │ └── RequestNotificationPermissionTest.kt
│ │ │ └── rules
│ │ │ ├── FirebasePushNotificationTestRule.kt
│ │ │ ├── ScreenshotRule.kt
│ │ │ └── LazyActivityScenarioRule.kt
│ └── test
│ │ └── java
│ │ ├── android
│ │ ├── os
│ │ │ └── Build.java
│ │ ├── text
│ │ │ └── TextUtils.java
│ │ └── util
│ │ │ ├── Log.java
│ │ │ └── Base64.java
│ │ └── bisq
│ │ └── android
│ │ └── tests
│ │ ├── ext
│ │ └── StringExtTest.kt
│ │ └── util
│ │ ├── DateUtilTest.kt
│ │ ├── QrUtilTest.kt
│ │ └── MaskingUtilTest.kt
└── proguard-rules.pro
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── .snyk
├── docs
└── tests
│ ├── README.md
│ ├── privacy.feature
│ ├── accessibility.feature
│ ├── pairing.feature
│ └── notifications.feature
├── .github
├── dependabot.yml
├── actions
│ └── get-avd-info
│ │ └── action.yml
└── workflows
│ └── release.yaml
├── .gitattributes
├── .editorconfig
├── gradle.properties
├── README.md
└── gradlew.bat
/testCommon/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | include ':testCommon'
3 |
--------------------------------------------------------------------------------
/version.properties:
--------------------------------------------------------------------------------
1 | majorVersion=1
2 | minorVersion=3
3 | patchVersion=7
4 | buildNumber=
5 |
--------------------------------------------------------------------------------
/images/welcome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/images/welcome.png
--------------------------------------------------------------------------------
/images/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/images/settings.png
--------------------------------------------------------------------------------
/images/notification_list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/images/notification_list.png
--------------------------------------------------------------------------------
/images/pairing_success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/images/pairing_success.png
--------------------------------------------------------------------------------
/images/scan_pairing_code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/images/scan_pairing_code.png
--------------------------------------------------------------------------------
/images/offer_taken_details.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/images/offer_taken_details.png
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/drawable/bisq_mark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/drawable/bisq_mark.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/checkmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/drawable/checkmark.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/
2 | .idea/
3 | *.iml
4 | app/build/
5 | app/release/
6 | app/google-services.json
7 | build/
8 | local.properties
9 | snyk
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/playstore_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/drawable/playstore_icon.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/assets/Font Awesome 5 Free-Solid-900.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/assets/Font Awesome 5 Free-Solid-900.otf
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bisq-network/bisqremote_Android/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.snyk:
--------------------------------------------------------------------------------
1 | ignore:
2 | SNYK-JAVA-COMGOOGLECODEGSON-1730327:
3 | - androidx.room:room-compiler@2.4.2 > androidx.room:room-migration@2.4.2 > com.google.code.gson:gson@2.8.0:
4 | reason: 'Currently no direct upgrade or patch'
5 | expires: '2022-10-01T00:00:00.000Z'
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Mar 28 15:32:07 PDT 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/docs/tests/README.md:
--------------------------------------------------------------------------------
1 | # Manual Tests
2 |
3 | The test scenarios detailed in these feature files are either not possible, not easy, or not worth
4 | the effort to automate so must be executed manually. If possible, they should be performed using a
5 | real Android device as opposed to an emulator.
6 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "monthly"
7 | - package-ecosystem: "gradle"
8 | directory: "/"
9 | schedule:
10 | interval: "monthly"
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and normalize line endings to LF
2 | # This will handle all files NOT found below
3 | * text=auto
4 |
5 | # These binary files should be left untouched
6 | # (binary is a macro for -text -diff)
7 | *.bmp binary
8 | *.gif binary
9 | *.ico binary
10 | *.jar binary
11 | *.jpg binary
12 | *.jpeg binary
13 | *.png binary
14 |
--------------------------------------------------------------------------------
/docs/tests/privacy.feature:
--------------------------------------------------------------------------------
1 | Feature: Privacy
2 | Privacy is of utmost importance. It is vital that no unnecessary data is retained.
3 |
4 | Scenario: App data is erased when deleting the app
5 | Given the app has been paired
6 | And the app has received notifications
7 | When deleting the app
8 | And reinstalling the app
9 | Then the app will load the Welcome view
10 | And the previous pairing and notifications will have been deleted
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete_white_24.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_debug_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/theme_res.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @string/dark_theme
5 | @string/light_theme
6 | @string/system_theme
7 |
8 |
9 | @string/dark_theme_preference_value
10 | @string/light_theme_preference_value
11 | @string/system_theme_preference_value
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_theme_white_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_restart_white_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_link_white_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_info_white_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
11 | [.idea/codeStyles/*.xml]
12 | indent_size = 2
13 | insert_final_newline = false
14 |
15 | [*.{kt,kts}]
16 | max_line_length = 120
17 | ktlint_code_style = android_studio
18 | ktlint_standard_import-ordering = disabled
19 | ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = unset
20 | ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = unset
21 | ktlint_standard_trailing-comma-on-call-site = disabled
22 | ktlint_standard_trailing-comma-on-declaration-site = disabled
23 | ktlint_standard_function-signature = disabled
24 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_stat_notifications.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/testCommon/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_qr_code_scanner_white_24.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | android.nonFinalResIds=false
10 | android.nonTransitiveRClass=false
11 | android.useAndroidX=true
12 | org.gradle.jvmargs=-Xmx1536m
13 | # When configured, Gradle will run in incubating parallel mode.
14 | # This option should only be used with decoupled projects. More details, visit
15 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
16 | # org.gradle.parallel=true
17 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/elements/ClickableElement.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.elements
19 |
20 | interface ClickableElement {
21 | fun click()
22 | }
23 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -dontwarn com.google.firebase.messaging.TopicOperation$TopicOperations
24 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/database/DebugLogLevel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.database
19 |
20 | enum class DebugLogLevel {
21 | DEBUG,
22 | INFO,
23 | WARN,
24 | ERROR
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/Constants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android
19 |
20 | const val BISQ_NETWORK_URL = "https://bisq.network"
21 | const val BISQ_MOBILE_URL = "https://docs.bisq.network/bisq-mobile"
22 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/model/DeviceStatus.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.model
19 |
20 | enum class DeviceStatus {
21 | UNPAIRED,
22 | PAIRED,
23 | ERASED,
24 | REMOTE_ERASED,
25 | NEEDS_REPAIR
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/test/java/android/os/Build.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package android.os;
19 |
20 | public class Build {
21 | public static final String MANUFACTURER = "My Manufacturer";
22 | public static final String MODEL = "My Model";
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
24 |
--------------------------------------------------------------------------------
/app/src/test/java/android/text/TextUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package android.text;
19 |
20 | import org.jetbrains.annotations.Nullable;
21 |
22 | public class TextUtils {
23 | public static boolean isEmpty(@Nullable CharSequence str) {
24 | return str == null || str.length() == 0;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/model/NotificationType.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.model
19 |
20 | enum class NotificationType {
21 | // setup
22 | SETUP_CONFIRMATION,
23 | ERASE,
24 |
25 | // from Bisq
26 | TRADE,
27 | DISPUTE,
28 | OFFER,
29 | PRICE,
30 | MARKET
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/PairingScanScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens
19 |
20 | import bisq.android.R
21 | import bisq.android.screens.elements.ButtonElement
22 |
23 | class PairingScanScreen : Screen() {
24 | val noWebcamButton = ButtonElement(R.id.pairing_scan_no_webcam_button)
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/Screen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens
19 |
20 | import android.content.Context
21 | import androidx.test.core.app.ApplicationProvider
22 |
23 | abstract class Screen {
24 | protected val applicationContext: Context = ApplicationProvider.getApplicationContext()
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/dialogs/PromptDialog.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.dialogs
19 |
20 | import android.R
21 | import bisq.android.screens.elements.ButtonElement
22 |
23 | open class PromptDialog(message: String) : Dialog(message) {
24 | val dismissButton = ButtonElement(R.id.button1)
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/PairingSendScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens
19 |
20 | import bisq.android.R
21 | import bisq.android.screens.elements.ButtonElement
22 |
23 | class PairingSendScreen : Screen() {
24 | val sendPairingTokenButton = ButtonElement(R.id.pairing_send_pairing_token_button)
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/model/NotificationMessage.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.model
19 |
20 | data class NotificationMessage(val magicValue: String, val initializationVector: String, val encryptedPayload: String) {
21 | companion object {
22 | const val BISQ_MESSAGE_ANDROID_MAGIC = "BisqMessageAndroid"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/dialogs/ChoicePromptDialog.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.dialogs
19 |
20 | import android.R
21 | import bisq.android.screens.elements.ButtonElement
22 |
23 | class ChoicePromptDialog(message: String) : Dialog(message) {
24 | val positiveButton = ButtonElement(R.id.button1)
25 | val negativeButton = ButtonElement(R.id.button2)
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/elements/ElementById.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.elements
19 |
20 | import android.view.View
21 | import androidx.test.espresso.matcher.ViewMatchers
22 | import org.hamcrest.Matcher
23 |
24 | abstract class ElementById(private val id: Int) : Element() {
25 | override fun getViewMatcher(): Matcher? = ViewMatchers.withId(id)
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/elements/ElementByText.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.elements
19 |
20 | import android.view.View
21 | import androidx.test.espresso.matcher.ViewMatchers
22 | import org.hamcrest.Matcher
23 |
24 | abstract class ElementByText(private val text: String) : Element() {
25 | override fun getViewMatcher(): Matcher? = ViewMatchers.withText(text)
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/PairingSuccessScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens
19 |
20 | import bisq.android.R
21 | import bisq.android.screens.dialogs.PermissionPrompt
22 | import bisq.android.screens.elements.ButtonElement
23 |
24 | class PairingSuccessScreen : Screen() {
25 | val pairingCompleteButton = ButtonElement(R.id.pairing_scan_pairing_complete_button)
26 | val permissionPrompt = PermissionPrompt()
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/settings/SettingsActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui.settings
19 |
20 | import bisq.android.R
21 | import bisq.android.ui.PairedBaseActivity
22 |
23 | class SettingsActivity : PairedBaseActivity() {
24 |
25 | override fun getRootLayoutId() = R.id.settings_layout
26 | override fun getStatusBarScrimId() = R.id.settings_status_bar_background
27 |
28 | override fun initView() {
29 | setContentView(R.layout.activity_settings)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/database/DebugLog.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.database
19 |
20 | import androidx.room.Entity
21 | import androidx.room.PrimaryKey
22 | import bisq.android.util.DateUtil
23 |
24 | @Entity
25 | data class DebugLog(
26 | @PrimaryKey(autoGenerate = true)
27 | var id: Int = 0,
28 | var timestamp: Long,
29 | var level: DebugLogLevel,
30 | var text: String
31 | ) {
32 | override fun toString(): String = "${DateUtil.format(timestamp)} [$level] $text"
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/test/java/android/util/Log.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package android.util;
19 |
20 | public final class Log {
21 | public static int i(String tag, String msg) {
22 | System.out.print(tag + msg);
23 | return 0;
24 | }
25 |
26 | public static int w(String tag, String msg) {
27 | System.out.print(tag + msg);
28 | return 0;
29 | }
30 |
31 | public static int e(String tag, String msg) {
32 | System.out.print(tag + msg);
33 | return 0;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/database/DebugLogDao.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.database
19 |
20 | import androidx.lifecycle.LiveData
21 | import androidx.room.Dao
22 | import androidx.room.Insert
23 | import androidx.room.Query
24 |
25 | @Dao
26 | interface DebugLogDao {
27 | @get:Query("SELECT * FROM DebugLog ORDER BY timestamp DESC")
28 | val all: LiveData>
29 |
30 | @Insert
31 | suspend fun insert(debugLog: DebugLog): Long
32 |
33 | @Query("DELETE FROM DebugLog")
34 | suspend fun deleteAll()
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/test/java/android/util/Base64.java:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package android.util;
19 |
20 | public class Base64 {
21 |
22 | public static String encodeToString(byte[] input, int flags) {
23 | return java.util.Base64.getEncoder().encodeToString(input);
24 | }
25 |
26 | public static byte[] encode(byte[] input, int flags) {
27 | return java.util.Base64.getEncoder().encode(input);
28 | }
29 |
30 | public static byte[] decode(String str, int flags) {
31 | return java.util.Base64.getDecoder().decode(str);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/testCommon/build.gradle:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2 | import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile
3 |
4 | apply plugin: 'com.android.library'
5 | apply plugin: 'kotlin-android'
6 | apply plugin: 'org.jlleitschuh.gradle.ktlint'
7 | apply plugin: 'io.gitlab.arturbosch.detekt'
8 |
9 | repositories {
10 | google()
11 | mavenCentral()
12 | }
13 |
14 | android {
15 | namespace 'bisq.android.testCommon'
16 | compileSdk 36
17 |
18 | defaultConfig {
19 | minSdk 26
20 | }
21 |
22 | compileOptions {
23 | sourceCompatibility JavaVersion.VERSION_17
24 | targetCompatibility JavaVersion.VERSION_17
25 | }
26 |
27 | packagingOptions {
28 | resources.excludes.add("META-INF/*")
29 | }
30 | }
31 |
32 | tasks.withType(KotlinJvmCompile).configureEach {
33 | compilerOptions {
34 | jvmTarget.set(JvmTarget.JVM_17)
35 | }
36 | }
37 |
38 | dependencies {
39 | detektPlugins('io.gitlab.arturbosch.detekt:detekt-formatting:1.23.8')
40 |
41 | implementation platform('com.google.firebase:firebase-bom:34.1.0')
42 | implementation 'com.google.firebase:firebase-messaging:25.0.0'
43 |
44 | implementation 'io.mockk:mockk:1.14.5'
45 | implementation 'junit:junit:4.13.2'
46 | }
47 |
--------------------------------------------------------------------------------
/docs/tests/accessibility.feature:
--------------------------------------------------------------------------------
1 | Feature: Accessibility
2 | Accessibility options alter the presentation of components within the UI. As a result, it is
3 | important that the app behaves appropriately when using accessibility options.
4 |
5 | Scenario: Changing system font settings is reflected within the app
6 | Given the system font settings have been changed
7 | When navigating the app
8 | Then the app uses the updated font settings and behaves appropriately
9 |
10 | Scenario: Increasing system text/zoom settings is reflected within the app
11 | Given the system text/zoom settings have been increased
12 | When navigating the app
13 | Then the app uses the updated text/zoom settings and behaves appropriately
14 |
15 | Scenario: Decreasing system text/zoom settings is reflected within the app
16 | Given the system text/zoom settings have been decreased
17 | When navigating the app
18 | Then the app uses the updated text/zoom settings and behaves appropriately
19 |
20 | Scenario: Changing system color settings is reflected within the app
21 | Given the system color settings have been changed
22 | When navigating the app
23 | Then the app uses the updated color settings and behaves appropriately
24 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/RequestNotificationPermissionScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens
19 |
20 | import bisq.android.R
21 | import bisq.android.screens.dialogs.PermissionPrompt
22 | import bisq.android.screens.elements.ButtonElement
23 |
24 | class RequestNotificationPermissionScreen : Screen() {
25 | val enableNotificationsButton = ButtonElement(R.id.request_notification_permission_button)
26 | val skipPermissionButton = ButtonElement(R.id.skip_request_notification_permission_button)
27 | val permissionPrompt = PermissionPrompt()
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/UnpairedBaseActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui
19 |
20 | import android.content.Intent
21 | import bisq.android.ui.pairing.PairingSuccessActivity
22 |
23 | abstract class UnpairedBaseActivity : BaseActivity() {
24 |
25 | fun pairingConfirmed() {
26 | this.runOnUiThread {
27 | playTone()
28 | }
29 | val intent = Intent(this, PairingSuccessActivity::class.java)
30 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
31 | startActivity(intent)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #25B135
4 | #FFFFFF
5 | #00530F
6 | #FFFFFF
7 | #DDDDDD
8 | #25B135
9 | #A2DA8D
10 |
11 | #53634E
12 | #FFFFFF
13 | #D6E8CE
14 | #111F0F
15 | #DDDDDD
16 | #8E8E8E
17 |
18 | #FCFDF6
19 | #1A1C19
20 | #B7B7B7
21 |
22 | #FCFDF6
23 | #1A1C19
24 |
25 | #FFB4AB
26 | #690005
27 | #93000A
28 | #FFDAD6
29 |
30 | #000000
31 | #808080
32 |
33 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/elements/ButtonElement.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.elements
19 |
20 | import androidx.test.espresso.Espresso
21 | import androidx.test.espresso.action.ViewActions
22 | import androidx.test.espresso.assertion.ViewAssertions
23 | import androidx.test.espresso.matcher.ViewMatchers
24 |
25 | class ButtonElement(private val id: Int) :
26 | ElementById(id),
27 | ClickableElement {
28 |
29 | override fun click() {
30 | Espresso.onView(ViewMatchers.withId(id))
31 | .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
32 | .perform(ViewActions.click())
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #25B135
4 | #FFFFFF
5 | #00530F
6 | #FFFFFF
7 | #DDDDDD
8 | #25B135
9 | #A2DA8D
10 |
11 | #BACCB3
12 | #253423
13 | #2B2B2B
14 | #CBCBCB
15 | #DDDDDD
16 | #8E8E8E
17 |
18 | #262626
19 | #F2F2F2
20 | #585858
21 |
22 | #262626
23 | #F2F2F2
24 |
25 | #FFB4AB
26 | #690005
27 | #93000A
28 | #FFDAD6
29 |
30 | #FFFFFF
31 | #808080
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/util/DateUtil.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.util
19 |
20 | import java.text.SimpleDateFormat
21 | import java.util.Locale
22 | import java.util.TimeZone
23 |
24 | object DateUtil {
25 | private val LOCALE = Locale.US
26 | private const val PATTERN = "yyyy-MM-dd HH:mm:ss"
27 |
28 | fun format(
29 | date: Long,
30 | locale: Locale = LOCALE,
31 | pattern: String = PATTERN,
32 | timezone: TimeZone = TimeZone.getDefault()
33 | ): String? {
34 | val formatter = SimpleDateFormat(pattern, locale)
35 | formatter.timeZone = timezone
36 | return formatter.format(date)
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/elements/SelectionElement.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.elements
19 |
20 | import androidx.test.espresso.Espresso
21 | import androidx.test.espresso.action.ViewActions
22 | import androidx.test.espresso.assertion.ViewAssertions
23 | import androidx.test.espresso.matcher.ViewMatchers
24 |
25 | class SelectionElement(private val text: String) :
26 | ElementByText(text),
27 | ClickableElement {
28 |
29 | override fun click() {
30 | Espresso.onView(ViewMatchers.withText(text))
31 | .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
32 | .perform(ViewActions.click())
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/elements/PreferenceElement.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.elements
19 |
20 | import androidx.test.espresso.Espresso
21 | import androidx.test.espresso.action.ViewActions
22 | import androidx.test.espresso.assertion.ViewAssertions
23 | import androidx.test.espresso.matcher.ViewMatchers
24 |
25 | class PreferenceElement(private val text: String) :
26 | ElementByText(text),
27 | ClickableElement {
28 |
29 | override fun click() {
30 | Espresso.onView(ViewMatchers.withText(text))
31 | .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
32 | .perform(ViewActions.click())
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/NotificationDetailScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens
19 |
20 | import bisq.android.R
21 | import bisq.android.screens.elements.ButtonElement
22 | import bisq.android.screens.elements.TextElement
23 |
24 | class NotificationDetailScreen : Screen() {
25 | val title = TextElement(R.id.notification_detail_title)
26 | val message = TextElement(R.id.notification_detail_message)
27 | val action = TextElement(R.id.notification_detail_action)
28 | val eventTime = TextElement(R.id.notification_detail_event_time)
29 | val receivedTime = TextElement(R.id.notification_detail_received_time)
30 | val deleteButton = ButtonElement(R.id.notification_delete_button)
31 | }
32 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/tests/PairingScanTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.tests
19 |
20 | import androidx.test.espresso.intent.Intents.intended
21 | import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
22 | import androidx.test.ext.junit.runners.AndroidJUnit4
23 | import bisq.android.ui.pairing.PairingSendActivity
24 | import org.junit.Test
25 | import org.junit.runner.RunWith
26 |
27 | @RunWith(AndroidJUnit4::class)
28 | class PairingScanTest : BaseTest() {
29 | @Test
30 | fun clickNoWebcamButtonLoadsPairingSendActivity() {
31 | pairingScanActivityRule.launch()
32 |
33 | pairingScanScreen.noWebcamButton.click()
34 | intended(hasComponent(PairingSendActivity::class.java.name))
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Bisq Notification Android App
2 |
3 | Since Bisq is a desktop-based application, this Android app enables you to pair it with your desktop
4 | application and receive important notifications such as trade updates and offer alerts when you are
5 | not near your computer.
6 |
7 | ## Prerequisites
8 |
9 | In order to pair the app and receive notifications, you will need to obtain an appropriate
10 | `google-services.json` file and place it under the app/ directory. Refer to
11 | [firebase documentation](https://firebase.google.com/docs/android/setup#add-config-file)
12 | for more information.
13 |
14 | > Note, the `google-services.json` file needs to correspond to the `fcmServiceAccountKey.json`
15 | > used by the [bisq-relay](https://github.com/bisq-network/bisq-relay) service.
16 |
17 | ## Architectural Design
18 |
19 | For information on the architectural design, refer to the
20 | [Bisq Remote Specification](https://github.com/bisq-network/bisqremote/wiki/Specification).
21 |
22 | ## Screenshots
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ext/BroadcastReceiverExt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ext
19 |
20 | import android.content.BroadcastReceiver
21 | import kotlinx.coroutines.CoroutineScope
22 | import kotlinx.coroutines.DelicateCoroutinesApi
23 | import kotlinx.coroutines.GlobalScope
24 | import kotlinx.coroutines.launch
25 |
26 | @OptIn(DelicateCoroutinesApi::class)
27 | fun BroadcastReceiver.goAsync(
28 | coroutineScope: CoroutineScope = GlobalScope,
29 | block: suspend () -> Unit
30 | ) {
31 | val result = goAsync()
32 | coroutineScope.launch {
33 | try {
34 | block()
35 | } finally {
36 | // Always call finish(), even if the coroutineScope was cancelled
37 | result?.finish()
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/util/MaskingUtil.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.util
19 |
20 | object MaskingUtil {
21 | @Suppress("ReturnCount")
22 | fun maskSensitive(value: String?, visibleChars: Int = 5, maskChar: Char = '*'): String {
23 | if (value.isNullOrEmpty()) return value ?: ""
24 |
25 | // If the value is too short to be masked, mask the whole thing
26 | val minVisible = visibleChars * 2
27 | if (value.length <= minVisible) {
28 | return maskChar.toString().repeat(value.length)
29 | }
30 |
31 | val firstPart = value.take(visibleChars)
32 | val lastPart = value.takeLast(visibleChars)
33 | val maskedMiddle = maskChar.toString().repeat(value.length - (visibleChars * 2))
34 |
35 | return "$firstPart$maskedMiddle$lastPart"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/circular_progressbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
16 |
22 |
23 |
24 |
25 |
26 |
31 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/database/DebugLogRepository.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.database
19 |
20 | import android.content.Context
21 | import androidx.lifecycle.LiveData
22 | import kotlinx.coroutines.coroutineScope
23 | import kotlinx.coroutines.launch
24 |
25 | class DebugLogRepository(context: Context) {
26 |
27 | private val debugLogDao: DebugLogDao
28 |
29 | val allLogs: LiveData>
30 |
31 | init {
32 | val db = DebugLogDatabase.getDatabase(context)
33 | debugLogDao = db.debugLogDao()
34 | allLogs = debugLogDao.all
35 | }
36 |
37 | suspend fun insert(debugLog: DebugLog) = coroutineScope {
38 | launch {
39 | debugLogDao.insert(debugLog)
40 | }
41 | }
42 |
43 | suspend fun deleteAll() = coroutineScope {
44 | launch { debugLogDao.deleteAll() }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/debug/DebugViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui.debug
19 |
20 | import android.app.Application
21 | import androidx.lifecycle.AndroidViewModel
22 | import androidx.lifecycle.LiveData
23 | import androidx.lifecycle.viewModelScope
24 | import bisq.android.database.DebugLog
25 | import bisq.android.database.DebugLogRepository
26 | import kotlinx.coroutines.launch
27 |
28 | class DebugViewModel(application: Application) : AndroidViewModel(application) {
29 |
30 | private val repository: DebugLogRepository = DebugLogRepository(application)
31 |
32 | var allLogs: LiveData> = repository.allLogs
33 |
34 | fun insert(debugLog: DebugLog) = viewModelScope.launch {
35 | repository.insert(debugLog)
36 | }
37 |
38 | fun nukeTable() = viewModelScope.launch {
39 | repository.deleteAll()
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ext/StringExt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ext
19 |
20 | import android.text.TextUtils
21 |
22 | @Suppress("MagicNumber")
23 | fun String.hexStringToByteArray() = ByteArray(this.length / 2) {
24 | this.substring(it * 2, it * 2 + 2).toInt(16).toByte()
25 | }
26 |
27 | fun String.capitalizeEachWord(): String {
28 | if (TextUtils.isEmpty(this)) {
29 | return this
30 | }
31 | val arr = this.toCharArray()
32 | var capitalizeNext = true
33 |
34 | val phrase = StringBuilder()
35 | for (c in arr) {
36 | if (capitalizeNext && Character.isLetter(c)) {
37 | phrase.append(Character.toUpperCase(c))
38 | capitalizeNext = false
39 | continue
40 | } else if (Character.isWhitespace(c)) {
41 | capitalizeNext = true
42 | }
43 | phrase.append(c)
44 | }
45 |
46 | return phrase.toString()
47 | }
48 |
--------------------------------------------------------------------------------
/.github/actions/get-avd-info/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Get AVD Info'
2 | description: 'Get the Android Virtual Device (AVD) info based on its API level.'
3 | inputs:
4 | api-level:
5 | description: The API level for which to retrieve AVD info
6 | required: true
7 | outputs:
8 | target:
9 | description: Target of the system image
10 | value: ${{ steps.get-avd-target.outputs.target }}
11 | arch:
12 | description: CPU architecture of the system image
13 | value: ${{ steps.get-avd-arch.outputs.arch }}
14 | runs:
15 | using: "composite"
16 | steps:
17 | # Prefer ATD system images available in API 30+ as they are optimized for headless tests.
18 | # Google Play services is required and is available in the google_atd and google_apis images.
19 | # Note:
20 | # - API 27 does not provide a google_apis system image.
21 | # - Newer API's may not yet provide an ATD system image.
22 | - id: get-avd-target
23 | shell: bash
24 | run: |
25 | set -euo pipefail
26 | api="${{ inputs.api-level }}"
27 | target="google_apis"
28 | if [[ "$api" -eq 27 ]]; then
29 | target="default"
30 | elif [[ "$api" -ge 30 ]] && sdkmanager --list | grep -q "system-images;android-$api;google_atd;"; then
31 | target="google_atd"
32 | fi
33 | echo "target=$target" >> "$GITHUB_OUTPUT"
34 | # Prefer x86_64 architecture
35 | - id: get-avd-arch
36 | shell: bash
37 | run: echo "arch=x86_64" >> $GITHUB_OUTPUT
38 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/settings.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
11 |
12 |
17 |
18 |
22 |
23 |
27 |
28 |
32 |
33 |
37 |
38 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/elements/MenuItemElement.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.elements
19 |
20 | import androidx.test.espresso.Espresso
21 | import androidx.test.espresso.action.ViewActions
22 | import androidx.test.espresso.assertion.ViewAssertions
23 | import androidx.test.espresso.matcher.RootMatchers.isPlatformPopup
24 | import androidx.test.espresso.matcher.ViewMatchers
25 | import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
26 |
27 | class MenuItemElement(private val text: String) :
28 | ElementByText(text),
29 | ClickableElement {
30 |
31 | override fun click() {
32 | Espresso.openActionBarOverflowOrOptionsMenu(getInstrumentation().targetContext)
33 | Espresso.onView(ViewMatchers.withText(text))
34 | .inRoot(isPlatformPopup())
35 | .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
36 | .perform(ViewActions.click())
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/dialogs/ThemePromptDialog.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.dialogs
19 |
20 | import android.content.Context
21 | import androidx.test.core.app.ApplicationProvider
22 | import bisq.android.R
23 | import bisq.android.screens.elements.ButtonElement
24 | import bisq.android.screens.elements.SelectionElement
25 |
26 | class ThemePromptDialog : PromptDialog(message) {
27 | val darkThemeSelection = SelectionElement(applicationContext.getString(R.string.dark_theme))
28 | val lightThemeSelection = SelectionElement(applicationContext.getString(R.string.light_theme))
29 | val systemThemeSelection = SelectionElement(applicationContext.getString(R.string.system_theme))
30 | val cancelButton = ButtonElement(android.R.id.button2)
31 |
32 | companion object {
33 | private val applicationContext: Context = ApplicationProvider.getApplicationContext()
34 | val message: String = applicationContext.getString(R.string.theme)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/WelcomeScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens
19 |
20 | import bisq.android.BISQ_MOBILE_URL
21 | import bisq.android.R
22 | import bisq.android.screens.dialogs.ChoicePromptDialog
23 | import bisq.android.screens.dialogs.PromptDialog
24 | import bisq.android.screens.elements.ButtonElement
25 |
26 | class WelcomeScreen : Screen() {
27 | val pairButton = ButtonElement(R.id.welcome_pair_button)
28 | val learnMoreButton = ButtonElement(R.id.welcome_learn_more_button)
29 | val alertDialogGooglePlayServicesUnavailable = PromptDialog(
30 | applicationContext.resources.getString(R.string.google_play_services_unavailable)
31 | )
32 | val alertDialogCannotRetrieveDeviceToken = ChoicePromptDialog(
33 | applicationContext.resources.getString(R.string.cannot_retrieve_fcm_token)
34 | )
35 | val alertDialogLoadBisqMobileUrl = ChoicePromptDialog(
36 | applicationContext.resources.getString(R.string.load_web_page_confirmation, BISQ_MOBILE_URL)
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/dialogs/Dialog.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.dialogs
19 |
20 | import android.content.Context
21 | import androidx.test.core.app.ApplicationProvider
22 | import androidx.test.espresso.Espresso.onView
23 | import androidx.test.espresso.NoMatchingViewException
24 | import androidx.test.espresso.assertion.ViewAssertions.matches
25 | import androidx.test.espresso.matcher.ViewMatchers.Visibility
26 | import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
27 | import androidx.test.espresso.matcher.ViewMatchers.withText
28 |
29 | abstract class Dialog(private val message: String) {
30 | protected val applicationContext: Context = ApplicationProvider.getApplicationContext()
31 |
32 | fun isDisplayed(): Boolean =
33 | try {
34 | onView(withText(message)).check(
35 | matches(withEffectiveVisibility(Visibility.VISIBLE))
36 | )
37 | true
38 | } catch (_: NoMatchingViewException) {
39 | false
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/elements/Element.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.elements
19 |
20 | import android.view.View
21 | import androidx.test.espresso.Espresso
22 | import androidx.test.espresso.assertion.ViewAssertions
23 | import androidx.test.espresso.matcher.ViewMatchers
24 | import org.hamcrest.Matcher
25 |
26 | abstract class Element {
27 | abstract fun getViewMatcher(): Matcher?
28 |
29 | fun isDisplayed(): Boolean {
30 | try {
31 | Espresso.onView(getViewMatcher())
32 | .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
33 | } catch (e: AssertionError) {
34 | return false
35 | }
36 | return true
37 | }
38 |
39 | fun isEnabled(): Boolean {
40 | try {
41 | Espresso.onView(getViewMatcher())
42 | .check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
43 | .check(ViewAssertions.matches(ViewMatchers.isEnabled()))
44 | } catch (e: AssertionError) {
45 | return false
46 | }
47 | return true
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/elements/TextElement.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.elements
19 |
20 | import android.view.View
21 | import android.widget.TextView
22 | import androidx.test.espresso.Espresso
23 | import androidx.test.espresso.UiController
24 | import androidx.test.espresso.ViewAction
25 | import androidx.test.espresso.matcher.ViewMatchers
26 | import androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom
27 | import org.hamcrest.Matcher
28 |
29 | class TextElement(private val id: Int) : ElementById(id) {
30 | fun getText(): String {
31 | var text = String()
32 | Espresso.onView(ViewMatchers.withId(id)).perform(object : ViewAction {
33 | override fun getConstraints(): Matcher = isAssignableFrom(TextView::class.java)
34 |
35 | override fun getDescription(): String = "Text of the view"
36 |
37 | override fun perform(uiController: UiController, view: View) {
38 | val tv = view as TextView
39 | text = tv.text.toString()
40 | }
41 | })
42 | return text
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/database/DebugLogDatabase.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.database
19 |
20 | import android.content.Context
21 | import androidx.room.Database
22 | import androidx.room.Room
23 | import androidx.room.RoomDatabase
24 |
25 | @Database(entities = [DebugLog::class], version = 1, exportSchema = false)
26 | abstract class DebugLogDatabase : RoomDatabase() {
27 |
28 | abstract fun debugLogDao(): DebugLogDao
29 |
30 | companion object {
31 |
32 | private var instance: DebugLogDatabase? = null
33 |
34 | fun getDatabase(context: Context): DebugLogDatabase {
35 | if (instance == null) {
36 | synchronized(DebugLogDatabase::class.java) {
37 | if (instance == null) {
38 | instance = Room.databaseBuilder(
39 | context.applicationContext,
40 | DebugLogDatabase::class.java,
41 | "debug.db"
42 | ).build()
43 | }
44 | }
45 | }
46 | return instance!!
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/NotificationTableScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens
19 |
20 | import bisq.android.R
21 | import bisq.android.screens.dialogs.ChoicePromptDialog
22 | import bisq.android.screens.elements.MenuItemElement
23 | import bisq.android.screens.elements.RecyclerViewElement
24 |
25 | class NotificationTableScreen : Screen() {
26 | val addExampleNotificationsMenuItem =
27 | MenuItemElement(applicationContext.resources.getString(R.string.button_add_example_notifications))
28 | val markAllAsReadMenuItem =
29 | MenuItemElement(applicationContext.resources.getString(R.string.button_mark_as_read))
30 | val deleteAllMenuItem =
31 | MenuItemElement(applicationContext.resources.getString(R.string.button_delete_notifications))
32 | val settingsMenuItem =
33 | MenuItemElement(applicationContext.resources.getString(R.string.settings))
34 | val notificationRecylerView = RecyclerViewElement(R.id.notification_table_recycler_view)
35 | val alertDialogDeleteAll = ChoicePromptDialog(
36 | applicationContext.resources.getString(R.string.delete_all_notifications_confirmation)
37 | )
38 | }
39 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/UiUtil.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui
19 |
20 | import android.content.ActivityNotFoundException
21 | import android.content.Context
22 | import android.content.Intent
23 | import android.net.Uri
24 | import android.widget.Toast
25 | import bisq.android.R
26 |
27 | object UiUtil {
28 | fun loadWebPage(context: Context, uri: String) {
29 | DialogBuilder.choicePrompt(
30 | context,
31 | context.getString(R.string.confirm),
32 | context.getString(R.string.load_web_page_confirmation, uri),
33 | context.getString(R.string.yes),
34 | context.getString(R.string.no),
35 | { _, _ ->
36 | try {
37 | context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(uri)))
38 | } catch (ignored: ActivityNotFoundException) {
39 | Toast.makeText(
40 | context,
41 | context.getString(R.string.cannot_launch_browser),
42 | Toast.LENGTH_LONG
43 | ).show()
44 | }
45 | }
46 | ).show()
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/database/NotificationDatabase.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.database
19 |
20 | import android.content.Context
21 | import androidx.room.Database
22 | import androidx.room.Room
23 | import androidx.room.RoomDatabase
24 |
25 | @Database(entities = [BisqNotification::class], version = 1, exportSchema = false)
26 | abstract class NotificationDatabase : RoomDatabase() {
27 |
28 | abstract fun bisqNotificationDao(): BisqNotificationDao
29 |
30 | companion object {
31 |
32 | private var instance: NotificationDatabase? = null
33 |
34 | fun getDatabase(context: Context): NotificationDatabase {
35 | if (instance == null) {
36 | synchronized(NotificationDatabase::class.java) {
37 | if (instance == null) {
38 | instance = Room.databaseBuilder(
39 | context.applicationContext,
40 | NotificationDatabase::class.java,
41 | "notifications.db"
42 | ).build()
43 | }
44 | }
45 | }
46 | return instance!!
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/PairedBaseActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui
19 |
20 | import android.content.Intent
21 | import android.widget.Toast
22 | import bisq.android.model.Device
23 | import bisq.android.model.DeviceStatus
24 | import bisq.android.ui.welcome.WelcomeActivity
25 |
26 | abstract class PairedBaseActivity : BaseActivity() {
27 |
28 | override fun onStart() {
29 | super.onStart()
30 | if (Device.instance.status != DeviceStatus.PAIRED) {
31 | val intent = Intent(this, WelcomeActivity::class.java)
32 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
33 | startActivity(intent)
34 | }
35 | }
36 |
37 | fun pairingRemoved(toastMessage: String) {
38 | this.runOnUiThread {
39 | playTone()
40 | Toast.makeText(
41 | this,
42 | toastMessage,
43 | Toast.LENGTH_LONG
44 | ).show()
45 | val intent = Intent(this, WelcomeActivity::class.java)
46 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
47 | startActivity(intent)
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | # Allows for running this workflow manually from the Actions tab
5 | workflow_dispatch:
6 |
7 | jobs:
8 | create_release:
9 | name: Create release
10 | runs-on: ubuntu-latest
11 | timeout-minutes: 10
12 | steps:
13 | - name: Download artifacts from latest master workflow
14 | uses: dawidd6/action-download-artifact@20319c5641d495c8a52e688b7dc5fada6c3a9fbc
15 | with:
16 | github_token: ${{ secrets.GITHUB_TOKEN }}
17 | workflow: master.yaml
18 | workflow_conclusion: success
19 |
20 | - name: Read VERSION file
21 | id: get_version
22 | run: echo "version=$(cat VERSION/VERSION)" >> $GITHUB_OUTPUT
23 |
24 | - name: Create release
25 | id: create_release
26 | uses: actions/create-release@0cb9c9b65d5d1901c1f53e5e66eaf4afd303e70e
27 | env:
28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29 | with:
30 | tag_name: ${{ steps.get_version.outputs.version }}
31 | release_name: ${{ steps.get_version.outputs.version }}
32 | draft: false
33 | prerelease: false
34 |
35 | - name: Upload signed APK to release
36 | id: upload_release_asset
37 | uses: actions/upload-release-asset@e8f9f06c4b078e705bd2ea027f0926603fc9b4d5
38 | env:
39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40 | with:
41 | upload_url: ${{ steps.create_release.outputs.upload_url }}
42 | asset_path: ./bisq-notifications-release-signed.apk
43 | asset_name: bisq-notifications-release-signed.apk
44 | asset_content_type: application/zip
45 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_bisq_mark.xml:
--------------------------------------------------------------------------------
1 |
6 |
12 |
13 |
--------------------------------------------------------------------------------
/docs/tests/pairing.feature:
--------------------------------------------------------------------------------
1 | Feature: Pairing
2 | Pairing is the process by which you share a pairing token, generated by the app, with the Bisq
3 | client. The pairing token contains the device identifier as well as a private key to encrypt
4 | the notification messages. It is also possible to erase the pairing remotely from the Bisq
5 | client.
6 |
7 | Scenario: Erase pairing while app is running in foreground
8 | Given the app has been paired
9 | And the app is running in the foreground
10 | When clicking the erase button in either the Bisq client or bisqremote tool
11 | Then the app will load the Welcome view
12 | And show a toast message indicating the app was unpaired remotely
13 |
14 | Scenario: Erase pairing while app is running in background or is closed
15 | Given the app has been paired
16 | And the app is running in the background or is closed
17 | When clicking the erase button in either the Bisq client or bisqremote tool
18 | Then a notification will be shown in the system notification area
19 | And upon clicking the notification it will load the Welcome view
20 | And show a toast message indicating the app was unpaired remotely
21 |
22 | Scenario: Erase pairing removes existing notifications
23 | Given the app has been paired
24 | And has existing notifications
25 | When clicking the erase button in either the Bisq client or bisqremote tool
26 | And following the process to pair the app again
27 | Then all previously received notifications will not exist
28 |
29 | Scenario: Pairing after the app has been put in the background then foreground
30 | Given the app is not paired
31 | And the pairing token has been scanned or transferred
32 | And the app is placed in the background
33 | And the app is placed in the foreground
34 | When the pairing process is performed
35 | Then the app is paired successfully
36 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/tests/PairingSendTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.tests
19 |
20 | import android.content.Intent
21 | import androidx.test.espresso.intent.Intents.intended
22 | import androidx.test.espresso.intent.matcher.BundleMatchers.hasValue
23 | import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction
24 | import androidx.test.espresso.intent.matcher.IntentMatchers.hasExtras
25 | import androidx.test.ext.junit.runners.AndroidJUnit4
26 | import bisq.android.model.Device
27 | import org.junit.Test
28 | import org.junit.runner.RunWith
29 |
30 | @RunWith(AndroidJUnit4::class)
31 | class PairingSendTest : BaseTest() {
32 | @Test
33 | fun clickSendPairingTokenButton() {
34 | Device.instance.newToken(
35 | "fnWtGaJGSByKiPwT71O3Lo:APA91bGU05lvoKxvz3Y0fnFHytSveA_juVjq2QMY3_H9URqDsEp" +
36 | "LHGbLSFBN3wY7YdHDD3w52GECwRWuKGBJm1O1f5fJhVvcr1rJxo94aDjoWwsnkVp-ecWwh5YY_MQ6LRqbWzumCeX_"
37 | )
38 | pairingSendActivityRule.launch()
39 |
40 | pairingSendScreen.sendPairingTokenButton.click()
41 | intended(hasAction(Intent.ACTION_CHOOSER))
42 | intended(
43 | hasExtras(
44 | hasValue(
45 | hasExtras(
46 | hasValue(Device.instance.pairingToken())
47 | )
48 | )
49 | )
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/notification/NotificationViewModel.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui.notification
19 |
20 | import android.app.Application
21 | import androidx.lifecycle.AndroidViewModel
22 | import androidx.lifecycle.LiveData
23 | import androidx.lifecycle.viewModelScope
24 | import bisq.android.database.BisqNotification
25 | import bisq.android.database.NotificationRepository
26 | import kotlinx.coroutines.launch
27 |
28 | class NotificationViewModel(application: Application) : AndroidViewModel(application) {
29 |
30 | private val repository: NotificationRepository = NotificationRepository(application)
31 |
32 | var bisqNotifications: LiveData> = repository.allBisqNotifications
33 |
34 | fun insert(bisqNotification: BisqNotification) = viewModelScope.launch {
35 | repository.insert(bisqNotification)
36 | }
37 |
38 | fun delete(bisqNotification: BisqNotification) = viewModelScope.launch {
39 | repository.delete(bisqNotification)
40 | }
41 |
42 | fun nukeTable() = viewModelScope.launch {
43 | repository.deleteAll()
44 | }
45 |
46 | fun getFromUid(uid: Int): BisqNotification? = repository.getFromUid(uid)
47 |
48 | fun markAllAsRead() = viewModelScope.launch {
49 | repository.markAllAsRead()
50 | }
51 |
52 | fun markAsRead(uid: Int) = viewModelScope.launch {
53 | repository.markAsRead(uid)
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/test/java/bisq/android/tests/ext/StringExtTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.tests.ext
19 |
20 | import bisq.android.ext.capitalizeEachWord
21 | import bisq.android.ext.hexStringToByteArray
22 | import org.assertj.core.api.Assertions.assertThat
23 | import org.junit.Test
24 |
25 | class StringExtTest {
26 | @Test
27 | fun testHexStringToByteArray() {
28 | val hexString = "ace24f2c3e0848bd9e57f6b415ca08df6ec7c22692bd48a296fe4044759e5eff"
29 | val bytearray = (hexString).hexStringToByteArray()
30 | assertThat(bytearray.asList().toString())
31 | .isEqualTo(
32 | "[-84, -30, 79, 44, 62, 8, 72, -67, -98, 87, -10, -76, 21, -54, 8, " +
33 | "-33, 110, -57, -62, 38, -110, -67, 72, -94, -106, -2, 64, 68, 117, -98, 94, -1]"
34 | )
35 | }
36 |
37 | @Test
38 | fun testCapitalizeSingleWord() {
39 | val test = "testcapitalizestring".capitalizeEachWord()
40 | assertThat(test)
41 | .isEqualTo("Testcapitalizestring")
42 | }
43 |
44 | @Test
45 | fun testCapitalizeMultipleWords() {
46 | val test = "test capitalize string".capitalizeEachWord()
47 | assertThat(test)
48 | .isEqualTo("Test Capitalize String")
49 | }
50 |
51 | @Test
52 | fun testCapitalizeEmptyString() {
53 | val test = "".capitalizeEachWord()
54 | assertThat(test).isEmpty()
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/SettingsScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens
19 |
20 | import bisq.android.BISQ_MOBILE_URL
21 | import bisq.android.BISQ_NETWORK_URL
22 | import bisq.android.R
23 | import bisq.android.screens.dialogs.ChoicePromptDialog
24 | import bisq.android.screens.dialogs.ThemePromptDialog
25 | import bisq.android.screens.elements.PreferenceElement
26 |
27 | class SettingsScreen : Screen() {
28 | val themePreference = PreferenceElement(applicationContext.getString(R.string.theme))
29 | val themePromptDialog = ThemePromptDialog()
30 | val resetPairingPreference = PreferenceElement(applicationContext.getString(R.string.reset_pairing))
31 | val alertDialogResetPairing = ChoicePromptDialog(
32 | applicationContext.resources.getString(R.string.register_again_confirmation)
33 | )
34 | val scanPairingTokenPreference = PreferenceElement(applicationContext.getString(R.string.scan_pairing_token))
35 | val aboutBisqPreference = PreferenceElement(applicationContext.getString(R.string.about_bisq))
36 | val aboutAppPreference = PreferenceElement(applicationContext.getString(R.string.about_this_app))
37 | val alertDialogLoadBisqNetworkUrl = ChoicePromptDialog(
38 | applicationContext.resources.getString(R.string.load_web_page_confirmation, BISQ_NETWORK_URL)
39 | )
40 | val alertDialogLoadBisqMobileUrl = ChoicePromptDialog(
41 | applicationContext.resources.getString(R.string.load_web_page_confirmation, BISQ_MOBILE_URL)
42 | )
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/database/BisqNotificationDao.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.database
19 |
20 | import androidx.lifecycle.LiveData
21 | import androidx.room.Dao
22 | import androidx.room.Delete
23 | import androidx.room.Insert
24 | import androidx.room.Query
25 |
26 | @Dao
27 | interface BisqNotificationDao {
28 | @get:Query("SELECT * FROM BisqNotification ORDER BY sentDate DESC")
29 | val allData: LiveData>
30 |
31 | @Query("SELECT * FROM BisqNotification ORDER BY sentDate DESC")
32 | suspend fun getAll(): List
33 |
34 | @Query("SELECT * FROM BisqNotification WHERE uid=:uid")
35 | suspend fun getFromUid(uid: Int): BisqNotification
36 |
37 | @Insert
38 | suspend fun insert(bisqNotification: BisqNotification): Long
39 |
40 | @Query(
41 | """
42 | DELETE FROM BisqNotification
43 | WHERE rowid NOT IN (
44 | SELECT MIN(rowid)
45 | FROM BisqNotification
46 | GROUP BY version, type, title, message, actionRequired, txId, sentDate
47 | )
48 | """
49 | )
50 | suspend fun removeDuplicates()
51 |
52 | @Delete
53 | suspend fun delete(bisqNotification: BisqNotification)
54 |
55 | @Query("DELETE FROM BisqNotification")
56 | suspend fun deleteAll()
57 |
58 | @Query("UPDATE BisqNotification SET read=1")
59 | suspend fun markAllAsRead()
60 |
61 | @Query("UPDATE BisqNotification SET read=1 WHERE uid=:uid")
62 | suspend fun markAsRead(uid: Int)
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/pairing/PairingSendActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui.pairing
19 |
20 | import android.content.Intent
21 | import android.widget.Button
22 | import android.widget.TextView
23 | import bisq.android.R
24 | import bisq.android.model.Device
25 | import bisq.android.ui.UnpairedBaseActivity
26 |
27 | class PairingSendActivity : UnpairedBaseActivity() {
28 |
29 | private lateinit var sendPairingTokenInstructions: TextView
30 | private lateinit var sendPairingTokenButton: Button
31 |
32 | override fun getRootLayoutId() = R.id.pairing_send_layout
33 | override fun getStatusBarScrimId() = R.id.pairing_send_status_bar_background
34 |
35 | override fun initView() {
36 | setContentView(R.layout.activity_pairing_send)
37 |
38 | sendPairingTokenInstructions = bind(R.id.pairing_send_pairing_token_instructions)
39 |
40 | sendPairingTokenButton = bind(R.id.pairing_send_pairing_token_button)
41 | sendPairingTokenButton.setOnClickListener {
42 | onSendPairingToken()
43 | }
44 | }
45 |
46 | private fun onSendPairingToken() {
47 | val sendIntent: Intent = Intent().apply {
48 | action = Intent.ACTION_SEND
49 | type = "text/plain"
50 | putExtra(Intent.EXTRA_SUBJECT, getString(R.string.send_pairing_subject))
51 | putExtra(Intent.EXTRA_TEXT, Device.instance.pairingToken())
52 | }
53 | startActivity(Intent.createChooser(sendIntent, getString(R.string.send_pairing_token)))
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/screens/dialogs/PermissionPrompt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.screens.dialogs
19 |
20 | import androidx.test.platform.app.InstrumentationRegistry
21 | import androidx.test.uiautomator.UiDevice
22 | import androidx.test.uiautomator.UiSelector
23 |
24 | class PermissionPrompt {
25 | private val device
26 | get() = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
27 | private val textElement
28 | get() = device.findObject(UiSelector().index(1))
29 | private val grantPermissionButton
30 | get() = device.findObject(UiSelector().textMatches("(?:Allow|ALLOW)"))
31 | private val denyPermissionButton
32 | get() = device.findObject(UiSelector().textMatches("(?:Don’t allow|DON’T ALLOW)"))
33 |
34 | fun isDisplayed(): Boolean = grantPermissionButton.exists() && denyPermissionButton.exists()
35 |
36 | fun text(): String {
37 | if (!textElement.exists()) {
38 | throw IllegalStateException("Text element does not exist")
39 | }
40 | return textElement.text
41 | }
42 |
43 | fun grantPermission() {
44 | if (!grantPermissionButton.exists()) {
45 | throw IllegalStateException("Grant permissions button does not exist")
46 | }
47 | grantPermissionButton.click()
48 | }
49 |
50 | fun denyPermission() {
51 | if (!grantPermissionButton.exists()) {
52 | throw IllegalStateException("Deny permissions button does not exist")
53 | }
54 | denyPermissionButton.click()
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/database/BisqNotification.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.database
19 |
20 | import androidx.room.ColumnInfo
21 | import androidx.room.Entity
22 | import androidx.room.PrimaryKey
23 | import kotlinx.serialization.Serializable
24 |
25 | @Entity
26 | @Serializable
27 | data class BisqNotification(
28 | @PrimaryKey(autoGenerate = true)
29 | var uid: Int = 0,
30 |
31 | @ColumnInfo(name = "version")
32 | var version: Int = 0,
33 |
34 | @ColumnInfo(name = "type")
35 | var type: String? = null,
36 |
37 | @ColumnInfo(name = "title")
38 | var title: String? = null,
39 |
40 | @ColumnInfo(name = "message")
41 | var message: String? = null,
42 |
43 | @ColumnInfo(name = "actionRequired")
44 | var actionRequired: String? = null,
45 |
46 | @ColumnInfo(name = "txId")
47 | var txId: String? = null,
48 |
49 | @ColumnInfo(name = "receivedDate")
50 | var receivedDate: Long = 0,
51 |
52 | @ColumnInfo(name = "sentDate")
53 | var sentDate: Long = 0,
54 |
55 | @ColumnInfo(name = "read")
56 | var read: Boolean = false
57 | ) {
58 | override fun equals(other: Any?): Boolean {
59 | if (this === other) return true
60 | if (other !is BisqNotification) return false
61 | return version == other.version &&
62 | type == other.type &&
63 | title == other.title &&
64 | message == other.message &&
65 | actionRequired == other.actionRequired &&
66 | txId == other.txId &&
67 | sentDate == other.sentDate
68 | }
69 |
70 | override fun hashCode(): Int = listOf(version, type, title, message, actionRequired, txId, sentDate)
71 | .hashCode()
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/util/QrUtil.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.util
19 |
20 | import android.graphics.Bitmap
21 | import android.graphics.Color
22 | import com.google.zxing.BarcodeFormat
23 | import com.google.zxing.common.BitMatrix
24 | import com.google.zxing.qrcode.QRCodeWriter
25 |
26 | object QrUtil {
27 |
28 | fun createQrImage(contents: String, width: Int = 1024, height: Int = 1024): Bitmap {
29 | val bitMatrix = buildBitMatrix(contents, width, height)
30 | val pixels = buildPixelArray(bitMatrix)
31 | return createBitmap(pixels, width, height)
32 | }
33 |
34 | private fun buildBitMatrix(
35 | contents: String,
36 | width: Int,
37 | height: Int
38 | ): BitMatrix {
39 | val bitMatrix: BitMatrix
40 | val writer = QRCodeWriter()
41 | bitMatrix = writer.encode(contents, BarcodeFormat.QR_CODE, width, height)
42 | return bitMatrix
43 | }
44 |
45 | private fun buildPixelArray(
46 | bitMatrix: BitMatrix
47 | ): IntArray {
48 | val pixels = IntArray(bitMatrix.width * bitMatrix.height)
49 | for (y in 0 until bitMatrix.height) {
50 | val offset = y * bitMatrix.width
51 | for (x in 0 until bitMatrix.width) {
52 | pixels[offset + x] = if (bitMatrix[x, y]) Color.BLACK else Color.WHITE
53 | }
54 | }
55 | return pixels
56 | }
57 |
58 | private fun createBitmap(
59 | pixels: IntArray,
60 | width: Int,
61 | height: Int
62 | ): Bitmap {
63 | val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
64 | bmp.setPixels(pixels, 0, width, 0, 0, width, height)
65 | return bmp
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/rules/FirebasePushNotificationTestRule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.rules
19 |
20 | import android.app.Service
21 | import android.content.Context
22 | import android.content.ContextWrapper
23 | import androidx.test.platform.app.InstrumentationRegistry
24 | import com.google.firebase.messaging.FirebaseMessagingService
25 | import com.google.firebase.messaging.RemoteMessage
26 | import org.junit.rules.TestWatcher
27 | import org.junit.runner.Description
28 |
29 | /**
30 | * This rule interacts with the lifecycle of FirebaseMessagingService,
31 | * effectively simulating the receipt of push notifications.
32 | */
33 | class FirebasePushNotificationTestRule(private val pushService: FirebaseMessagingService) : TestWatcher() {
34 |
35 | companion object {
36 | private const val FIREBASE_PUSH_TOKEN = "mocked_token_value"
37 | }
38 |
39 | override fun starting(description: Description) {
40 | super.starting(description)
41 | pushService.attachBaseContext()
42 | pushService.onCreate()
43 | pushService.onNewToken(FIREBASE_PUSH_TOKEN)
44 | }
45 |
46 | override fun finished(description: Description) {
47 | pushService.onDestroy()
48 | super.finished(description)
49 | }
50 |
51 | fun onNewToken(token: String) = pushService.onNewToken(token)
52 |
53 | fun sendPush(remoteMessage: RemoteMessage) = pushService.onMessageReceived(remoteMessage)
54 | }
55 |
56 | internal fun Service.attachBaseContext() {
57 | val context = InstrumentationRegistry.getInstrumentation().targetContext
58 |
59 | val attachBaseContextMethod = ContextWrapper::class.java.getDeclaredMethod("attachBaseContext", Context::class.java)
60 | attachBaseContextMethod.isAccessible = true
61 |
62 | attachBaseContextMethod.invoke(this, context)
63 | }
64 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/ThemeProvider.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui
19 |
20 | import android.app.UiModeManager
21 | import android.content.Context
22 | import androidx.appcompat.app.AppCompatDelegate
23 | import androidx.preference.PreferenceManager
24 | import bisq.android.R
25 | import java.security.InvalidParameterException
26 |
27 | class ThemeProvider(private val context: Context) {
28 | fun getThemeFromPreferences(): Int {
29 | val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
30 | val selectedTheme = sharedPreferences.getString(
31 | context.getString(R.string.theme_preferences_key),
32 | context.getString(R.string.system_theme_preference_value)
33 | )
34 |
35 | return selectedTheme?.let {
36 | getTheme(it)
37 | } ?: AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
38 | }
39 |
40 | fun getThemeDescriptionForPreference(preferenceValue: String?): String =
41 | when (preferenceValue) {
42 | context.getString(R.string.dark_theme_preference_value) ->
43 | context.getString(R.string.dark_theme_description)
44 |
45 | context.getString(R.string.light_theme_preference_value) ->
46 | context.getString(R.string.light_theme_description)
47 |
48 | else -> context.getString(R.string.system_theme_description)
49 | }
50 |
51 | fun getTheme(selectedTheme: String): Int = when (selectedTheme) {
52 | context.getString(R.string.dark_theme_preference_value) -> UiModeManager.MODE_NIGHT_YES
53 | context.getString(R.string.light_theme_preference_value) -> UiModeManager.MODE_NIGHT_NO
54 | context.getString(R.string.system_theme_preference_value) -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
55 | else -> throw InvalidParameterException("Theme not defined for $selectedTheme")
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
19 |
20 |
21 |
29 |
30 |
42 |
43 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/database/NotificationRepository.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.database
19 |
20 | import android.content.Context
21 | import androidx.lifecycle.LiveData
22 | import kotlinx.coroutines.coroutineScope
23 | import kotlinx.coroutines.launch
24 | import kotlinx.coroutines.runBlocking
25 |
26 | class NotificationRepository(context: Context) {
27 |
28 | private val bisqNotificationDao: BisqNotificationDao
29 |
30 | val allBisqNotifications: LiveData>
31 |
32 | init {
33 | val db = NotificationDatabase.getDatabase(context)
34 | bisqNotificationDao = db.bisqNotificationDao()
35 | allBisqNotifications = bisqNotificationDao.allData
36 | }
37 |
38 | suspend fun insert(bisqNotification: BisqNotification) = coroutineScope {
39 | launch {
40 | bisqNotificationDao.insert(bisqNotification)
41 | // This is a hack to prevent duplicate entries.
42 | // All other attempts at enforcing uniqueness were unsuccessful.
43 | bisqNotificationDao.removeDuplicates()
44 | }
45 | }
46 |
47 | suspend fun delete(bisqNotification: BisqNotification) = coroutineScope {
48 | launch { bisqNotificationDao.delete(bisqNotification) }
49 | }
50 |
51 | fun getFromUid(uid: Int): BisqNotification? {
52 | var x: BisqNotification?
53 | runBlocking {
54 | coroutineScope {
55 | x = bisqNotificationDao.getFromUid(uid)
56 | }
57 | }
58 | return x
59 | }
60 |
61 | suspend fun deleteAll() = coroutineScope {
62 | launch { bisqNotificationDao.deleteAll() }
63 | }
64 |
65 | suspend fun markAllAsRead() = coroutineScope {
66 | launch { bisqNotificationDao.markAllAsRead() }
67 | }
68 |
69 | suspend fun markAsRead(uid: Int) = coroutineScope {
70 | launch { bisqNotificationDao.markAsRead(uid) }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/test/java/bisq/android/tests/util/DateUtilTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.tests.util
19 |
20 | import bisq.android.util.DateUtil
21 | import org.assertj.core.api.Assertions.assertThat
22 | import org.junit.Assume.assumeFalse
23 | import org.junit.Assume.assumeTrue
24 | import org.junit.BeforeClass
25 | import org.junit.Test
26 | import java.util.Date
27 | import java.util.Locale
28 | import java.util.TimeZone
29 |
30 | class DateUtilTest {
31 |
32 | companion object {
33 | @BeforeClass
34 | @JvmStatic
35 | fun init() {
36 | TimeZone.setDefault(TimeZone.getTimeZone("UTC"))
37 | }
38 | }
39 |
40 | @Test
41 | fun testDefaultFormatReturnsFormattedString() {
42 | assertThat(DateUtil.format(1651974403000L))
43 | .isEqualTo("2022-05-08 01:46:43")
44 | }
45 |
46 | @Test
47 | fun testFormatWithSpecifiedLocaleReturnsFormattedString() {
48 | assertThat(DateUtil.format(1651974403000L, Locale.GERMAN))
49 | .isEqualTo("2022-05-08 01:46:43")
50 | }
51 |
52 | @Test
53 | fun testFormatWithSpecifiedPatternReturnsFormattedString() {
54 | assertThat(DateUtil.format(1651974403000L, pattern = "dd/MM/yyyy"))
55 | .isEqualTo("08/05/2022")
56 | }
57 |
58 | @Test
59 | fun testFormatWithSpecifiedTimezoneReturnsFormattedString() {
60 | val tz = TimeZone.getTimeZone("Pacific/Galapagos") // UTC-6 always
61 | assumeFalse("In daylight time", tz.inDaylightTime(Date()))
62 | assertThat(DateUtil.format(1651974403000L, timezone = tz))
63 | .isEqualTo("2022-05-07 19:46:43")
64 | }
65 |
66 | @Test
67 | fun testFormatWithSpecifiedTimezoneDSTReturnsFormattedString() {
68 | val tz = TimeZone.getTimeZone("Europe/Helsinki") // UTC+3 when DST in effect
69 | assumeTrue("Not in daylight time", tz.inDaylightTime(Date()))
70 | assertThat(DateUtil.format(1651974403000L, timezone = tz))
71 | .isEqualTo("2022-05-08 04:46:43")
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_notification_table.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
19 |
20 |
21 |
29 |
30 |
42 |
43 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/services/NotificationReceiver.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.services
19 |
20 | import android.content.BroadcastReceiver
21 | import android.content.Context
22 | import android.content.Intent
23 | import bisq.android.Logging
24 | import bisq.android.R
25 | import bisq.android.database.BisqNotification
26 | import bisq.android.ext.goAsync
27 | import bisq.android.model.Device
28 |
29 | class NotificationReceiver : BroadcastReceiver() {
30 | companion object {
31 | private const val TAG = "NotificationReceiver"
32 | }
33 |
34 | @Suppress("ReturnCount")
35 | override fun onReceive(context: Context, intent: Intent) {
36 | Logging().debug(TAG, "Notification received")
37 |
38 | if (Device.instance.key == null) {
39 | Logging().warn(TAG, "Ignoring received notification, device does not have a key")
40 | return
41 | }
42 |
43 | Logging().debug(TAG, "Processing notification")
44 | val bisqNotification: BisqNotification
45 | try {
46 | bisqNotification = NotificationProcessor.processNotification(
47 | intent.extras?.getString("encrypted").toString()
48 | )
49 | } catch (e: ProcessingException) {
50 | e.message?.let { Logging().error(TAG, it) }
51 | Intent().also { broadcastIntent ->
52 | broadcastIntent.action = context.getString(R.string.intent_receiver_action)
53 | broadcastIntent.putExtra(
54 | "error",
55 | context.getString(R.string.failed_to_process_notification)
56 | )
57 | context.sendBroadcast(broadcastIntent)
58 | }
59 | return
60 | }
61 |
62 | if (bisqNotification.type == null) {
63 | Logging().error(TAG, "Notification type is null: $bisqNotification")
64 | return
65 | }
66 |
67 | Logging().debug(TAG, "Handling ${bisqNotification.type} notification")
68 | goAsync {
69 | NotificationHandler.handleNotification(bisqNotification, context)
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/test/java/bisq/android/tests/util/QrUtilTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.tests.util
19 |
20 | import android.graphics.Bitmap
21 | import android.graphics.BitmapFactory
22 | import bisq.android.util.QrUtil
23 | import com.google.zxing.WriterException
24 | import org.junit.Before
25 | import org.junit.Test
26 | import org.junit.runner.RunWith
27 | import org.mockito.ArgumentMatchers.any
28 | import org.mockito.ArgumentMatchers.anyInt
29 | import org.mockito.ArgumentMatchers.anyString
30 | import org.mockito.Mock
31 | import org.mockito.Mockito.`when`
32 | import org.powermock.api.mockito.PowerMockito.mockStatic
33 | import org.powermock.core.classloader.annotations.PrepareForTest
34 | import org.powermock.modules.junit4.PowerMockRunner
35 |
36 | @RunWith(PowerMockRunner::class)
37 | @PrepareForTest(BitmapFactory::class, Bitmap::class)
38 | class QrUtilTest {
39 | @Mock
40 | private val bitmap: Bitmap? = null
41 |
42 | @Before
43 | fun setup() {
44 | mockStatic(Bitmap::class.java)
45 | mockStatic(BitmapFactory::class.java)
46 | `when`(Bitmap.createBitmap(anyInt(), anyInt(), any())).thenReturn(bitmap)
47 | `when`(BitmapFactory.decodeFile(anyString())).thenReturn(bitmap)
48 | }
49 |
50 | @Test
51 | fun testCreateQrImageWithSimpleContent() {
52 | QrUtil.createQrImage("text")
53 | }
54 |
55 | @Test
56 | fun testCreateQrImageWithCustomDimensions() {
57 | QrUtil.createQrImage("text", 640, 640)
58 | }
59 |
60 | @Test(expected = IllegalArgumentException::class)
61 | fun testCreateQrImageWithEmptyContentsThrowsException() {
62 | QrUtil.createQrImage("")
63 | }
64 |
65 | @Test(expected = WriterException::class)
66 | fun testCreateQrImageWithTooLongContents() {
67 | val charPool: List = ('a'..'z') + ('A'..'Z') + ('0'..'9')
68 | val stringLength = 10000
69 | val randomString =
70 | (1000..stringLength)
71 | .map { kotlin.random.Random.nextInt(0, charPool.size) }
72 | .map(charPool::get)
73 | .joinToString("")
74 | QrUtil.createQrImage(randomString)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/docs/tests/notifications.feature:
--------------------------------------------------------------------------------
1 | Feature: Notifications
2 | Notifications are received via Firebase Cloud Messaging (FCM) from the Bisq client and shown
3 | within the mobile app. They can be triggered manually either by the Bisq client or the
4 | bisqremote tool.
5 |
6 | Background: A pairing token generated by the app needs to be shared with the Bisq client.
7 | Given the app has been paired with the Bisq client
8 |
9 | Scenario: Notifications are shown within the app when it is running in the foreground
10 | Given the app is running in the foreground
11 | When a notification is received
12 | Then the notification will be shown directly in the app and not in the system notification area
13 |
14 | Scenario: Notifications are shown in system notification area when the app is running in the background
15 | Given the app is running in the background
16 | When a notification is received
17 | Then the notification will appear in the system notification area
18 | And it will not appear in the app until clicked on
19 |
20 | Scenario: Notifications are shown in system notification area when the app is closed
21 | Given the app is closed
22 | When a notification is received
23 | Then the notification will appear in the system notification area
24 | And it will not appear in the app until clicked on
25 |
26 | Scenario: Notifications are shown if clicked on while the app is running in the foreground
27 | Given the app is running in the background or is closed
28 | When a notification is received
29 | And the app is opened
30 | And the notification is clicked on within the system notification area
31 | Then the notification will be shown within the app
32 |
33 | Scenario: Multiple notifications are shown in the system notification area
34 | Given the app is running in the background or is closed
35 | When multiple notifications are received
36 | Then each notification will appear in the system notification area
37 |
38 | Scenario: Notifications are shown in system notification area when the app is not paired and not running in the foreground
39 | Given the app has reset its pairing
40 | And the app is running in the background or is closed
41 | When a notification is received using the old pairing token
42 | Then the notification will appear in the system notification area
43 | And opening the notification will show a toast message indicating it failed to process the notification
44 |
45 | Scenario: Toast message is shown when receiving a notification if the app is not paired and running in the foreground
46 | Given the app has reset its pairing
47 | And the app is running in the foreground
48 | When a notification is received using the old pairing token
49 | Then a toast message is shown indicating it failed to process the notification
50 |
--------------------------------------------------------------------------------
/app/src/test/java/bisq/android/tests/util/MaskingUtilTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.tests.util
19 |
20 | import bisq.android.util.MaskingUtil
21 | import org.assertj.core.api.Assertions.assertThat
22 | import org.junit.Test
23 |
24 | class MaskingUtilTest {
25 | @Test
26 | fun shouldMaskMiddleOfLongStringWithDefaultVisibleChars() {
27 | val result = MaskingUtil.maskSensitive("mySuperSecretPassword12345")
28 | assertThat(result).isEqualTo("mySup****************12345")
29 | }
30 |
31 | @Test
32 | fun shouldMaskMiddleOfStringWithCustomVisibleChars() {
33 | val result = MaskingUtil.maskSensitive("1234567890abcdef", 4)
34 | assertThat(result).isEqualTo("1234********cdef")
35 | }
36 |
37 | @Test
38 | fun shouldMaskMiddleOfStringWithCustomVisibleCharsAndCustomMaskCharacter() {
39 | val result = MaskingUtil.maskSensitive("VerySensitiveKeyHere", 6, '#')
40 | assertThat(result).isEqualTo("VerySe########eyHere")
41 | }
42 |
43 | @Test
44 | fun shouldMaskEntireStringIfShorterThanTwiceVisibleChars() {
45 | val result = MaskingUtil.maskSensitive("short", 3)
46 | assertThat(result).isEqualTo("*****")
47 | }
48 |
49 | @Test
50 | fun shouldMaskEntireStringIfShorterThanTwiceVisibleCharsWithCustomChar() {
51 | val result = MaskingUtil.maskSensitive("test", 3, '#')
52 | assertThat(result).isEqualTo("####")
53 | }
54 |
55 | @Test
56 | fun shouldReturnEmptyStringWhenInputIsNull() {
57 | val result = MaskingUtil.maskSensitive(null)
58 | assertThat(result).isEqualTo("")
59 | }
60 |
61 | @Test
62 | fun shouldReturnSameStringWhenInputIsEmpty() {
63 | val result = MaskingUtil.maskSensitive("")
64 | assertThat(result).isEqualTo("")
65 | }
66 |
67 | @Test
68 | fun shouldMaskEntireStringIfExactlyEqualToTwiceVisibleChars() {
69 | val result = MaskingUtil.maskSensitive("abcdefgh", 4)
70 | assertThat(result).isEqualTo("********")
71 | }
72 |
73 | @Test
74 | fun shouldHandleVeryLongStringWithLargeVisibleChars() {
75 | val result = MaskingUtil.maskSensitive("ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890", 10)
76 | assertThat(result).isEqualTo("ABCDEFGHIJ****************1234567890")
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/Logging.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android
19 |
20 | import android.util.Log
21 | import bisq.android.database.DebugLog
22 | import bisq.android.database.DebugLogLevel
23 | import bisq.android.database.DebugLogRepository
24 | import kotlinx.coroutines.CoroutineScope
25 | import kotlinx.coroutines.Dispatchers
26 | import kotlinx.coroutines.launch
27 |
28 | class Logging {
29 | companion object {
30 | private const val TAG = "Logging"
31 | }
32 |
33 | // Attempt to initialize debugRepository with the application context.
34 | // If the context is unavailable (e.g., in unit tests where Application is not initialized),
35 | // catch the exception and log a warning instead of throwing a NullPointerException.
36 | // This ensures that tests still work since trying to provide a mocked context is not straight forward.
37 | @Suppress("SwallowedException", "TooGenericExceptionCaught")
38 | private val debugRepository: DebugLogRepository? = try {
39 | val context = Application.applicationContext()
40 | DebugLogRepository(context)
41 | } catch (e: NullPointerException) {
42 | Log.w(TAG, "Skipping debugRepository initialization due to missing context")
43 | null
44 | }
45 |
46 | fun debug(tag: String, msg: String) {
47 | Log.d(tag, msg)
48 | insert(DebugLogLevel.DEBUG, msg)
49 | }
50 |
51 | fun info(tag: String, msg: String) {
52 | Log.i(tag, msg)
53 | insert(DebugLogLevel.INFO, msg)
54 | }
55 |
56 | fun warn(tag: String, msg: String) {
57 | Log.w(tag, msg)
58 | insert(DebugLogLevel.WARN, msg)
59 | }
60 |
61 | fun error(tag: String, msg: String) {
62 | Log.e(tag, msg)
63 | insert(DebugLogLevel.ERROR, msg)
64 | }
65 |
66 | private fun insert(level: DebugLogLevel, msg: String) {
67 | debugRepository?.let {
68 | CoroutineScope(Dispatchers.IO).launch {
69 | it.insert(
70 | DebugLog(
71 | timestamp = System.currentTimeMillis(),
72 | level = level,
73 | text = msg
74 | )
75 | )
76 | }
77 | } ?: Log.w(TAG, "Skipping log insert; debugRepository is unavailable")
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
10 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
49 |
52 |
55 |
56 |
59 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/services/IntentReceiver.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.services
19 |
20 | import android.app.Activity
21 | import android.content.BroadcastReceiver
22 | import android.content.Context
23 | import android.content.Intent
24 | import android.widget.Toast
25 | import bisq.android.Logging
26 | import bisq.android.R
27 | import bisq.android.model.Device
28 | import bisq.android.model.DeviceStatus
29 | import bisq.android.model.NotificationType
30 | import bisq.android.ui.PairedBaseActivity
31 | import bisq.android.ui.UnpairedBaseActivity
32 |
33 | class IntentReceiver(private val activity: Activity? = null) : BroadcastReceiver() {
34 | companion object {
35 | private const val TAG = "IntentReceiver"
36 | }
37 |
38 | @Suppress("ReturnCount")
39 | override fun onReceive(context: Context, intent: Intent) {
40 | Logging().debug(TAG, "Intent received")
41 |
42 | if (intent.action == null ||
43 | !intent.action.equals(context.getString(R.string.intent_receiver_action))
44 | ) {
45 | Logging().debug(
46 | TAG,
47 | "Ignoring intent, action is not " + context.getString(R.string.intent_receiver_action)
48 | )
49 | return
50 | }
51 |
52 | if (intent.hasExtra("error")) {
53 | val errorMessage = intent.getStringExtra("error")
54 | Logging().error(TAG, errorMessage!!)
55 | Toast.makeText(context, errorMessage, Toast.LENGTH_LONG).show()
56 | return
57 | }
58 |
59 | if (!intent.hasExtra("type")) {
60 | Logging().debug(TAG, "Ignoring intent, missing notification type")
61 | return
62 | }
63 |
64 | val type = intent.getStringExtra("type")
65 | if (type == NotificationType.SETUP_CONFIRMATION.name && activity is UnpairedBaseActivity) {
66 | Logging().debug(TAG, "Pairing confirmed")
67 | activity.pairingConfirmed()
68 | } else if (type == NotificationType.ERASE.name && activity is PairedBaseActivity) {
69 | Logging().debug(TAG, "Pairing removed")
70 | Device.instance.status = DeviceStatus.UNPAIRED
71 | activity.pairingRemoved(context.getString(R.string.pairing_erased))
72 | } else {
73 | Logging().debug(TAG, "Ignoring $type notification")
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_pairing_send.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
19 |
20 |
21 |
29 |
30 |
44 |
45 |
63 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/Application.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android
19 |
20 | import android.app.ActivityManager
21 | import android.app.NotificationChannel
22 | import android.app.NotificationManager
23 | import android.content.Context
24 | import android.content.pm.PackageManager
25 | import androidx.appcompat.app.AppCompatDelegate
26 | import androidx.multidex.MultiDexApplication
27 | import bisq.android.ui.ThemeProvider
28 |
29 | class Application : MultiDexApplication() {
30 | init {
31 | instance = this
32 | }
33 |
34 | companion object {
35 | private var instance: Application? = null
36 |
37 | fun applicationContext(): Context = instance!!.applicationContext
38 |
39 | fun getAppVersion(): String {
40 | val context: Context = applicationContext()
41 |
42 | val version = try {
43 | val pInfo = context.packageManager.getPackageInfo(context.packageName, 0)
44 | pInfo.versionName
45 | } catch (ignored: PackageManager.NameNotFoundException) {
46 | R.string.unknown
47 | }
48 | return version.toString()
49 | }
50 |
51 | fun isAppInBackground(): Boolean {
52 | val myProcess = ActivityManager.RunningAppProcessInfo()
53 | ActivityManager.getMyMemoryState(myProcess)
54 | return myProcess.importance != ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
55 | }
56 | }
57 |
58 | override fun onCreate() {
59 | super.onCreate()
60 | val theme = ThemeProvider(this).getThemeFromPreferences()
61 | AppCompatDelegate.setDefaultNightMode(theme)
62 |
63 | createNotificationChannel()
64 | }
65 |
66 | private fun createNotificationChannel() {
67 | val id = getString(R.string.default_notification_channel_id)
68 | val name = getString(R.string.notification_channel_name)
69 | val descriptionText = getString(R.string.notification_channel_description)
70 | val importance = NotificationManager.IMPORTANCE_DEFAULT
71 | val channel = NotificationChannel(id, name, importance)
72 | .apply { description = descriptionText }
73 |
74 | val notificationManager: NotificationManager =
75 | getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
76 | notificationManager.createNotificationChannel(channel)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
33 |
34 |
37 |
38 |
42 |
43 |
49 |
50 |
54 |
55 |
61 |
62 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/notification_cell.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
22 |
23 |
43 |
44 |
61 |
62 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/rules/ScreenshotRule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.rules
19 |
20 | import android.content.ContentValues
21 | import android.graphics.Bitmap
22 | import android.os.Build
23 | import android.os.Environment.DIRECTORY_PICTURES
24 | import android.os.Environment.getExternalStoragePublicDirectory
25 | import android.provider.MediaStore
26 | import androidx.test.core.app.takeScreenshot
27 | import androidx.test.platform.app.InstrumentationRegistry
28 | import org.junit.rules.TestWatcher
29 | import org.junit.runner.Description
30 | import java.io.File
31 | import kotlin.io.path.Path
32 | import kotlin.io.path.createDirectories
33 | import kotlin.io.path.pathString
34 |
35 | /**
36 | * Takes screenshots during test execution.
37 | */
38 | class ScreenshotRule : TestWatcher() {
39 | companion object {
40 | private val screenshotsPath = Path("bisq", "screenshots")
41 | }
42 |
43 | override fun failed(e: Throwable, description: Description) {
44 | super.failed(e, description)
45 | val parentFolderPath = Path("failures", description.className)
46 | saveScreenshot(takeScreenshot(), parentFolderPath.pathString, description.methodName)
47 | }
48 |
49 | private fun saveScreenshot(bitmap: Bitmap, parentFolderPath: String = "", screenshotName: String) {
50 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
51 | val contentValues = ContentValues().apply {
52 | put(MediaStore.MediaColumns.DISPLAY_NAME, "$screenshotName.png")
53 | put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
54 | put(
55 | MediaStore.MediaColumns.RELATIVE_PATH,
56 | Path(DIRECTORY_PICTURES, screenshotsPath.pathString, parentFolderPath).pathString
57 | )
58 | }
59 | val contentResolver = InstrumentationRegistry.getInstrumentation().targetContext.contentResolver
60 | val contentUri = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
61 | val uri = contentResolver.insert(contentUri, contentValues)
62 |
63 | contentResolver.openOutputStream(uri ?: return)?.use {
64 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
65 | }
66 | } else {
67 | val imagePath = Path(
68 | getExternalStoragePublicDirectory(DIRECTORY_PICTURES).path,
69 | screenshotsPath.pathString,
70 | parentFolderPath
71 | )
72 | imagePath.createDirectories()
73 | val image = File(imagePath.pathString, "$screenshotName.png")
74 | image.outputStream().use {
75 | bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/tests/NotificationDetailTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.tests
19 |
20 | import androidx.test.espresso.intent.Intents.intended
21 | import androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent
22 | import androidx.test.ext.junit.runners.AndroidJUnit4
23 | import bisq.android.ui.notification.NotificationDetailActivity
24 | import org.assertj.core.api.Assertions.assertThat
25 | import org.junit.Before
26 | import org.junit.Test
27 | import org.junit.runner.RunWith
28 |
29 | @RunWith(AndroidJUnit4::class)
30 | class NotificationDetailTest : BaseTest() {
31 | @Before
32 | override fun setup() {
33 | super.setup()
34 | pairDevice()
35 | }
36 |
37 | @Test
38 | fun notificationDetailsArePopulatedCorrectly() {
39 | notificationTableActivityRule.launch()
40 |
41 | notificationTableScreen.addExampleNotificationsMenuItem.click()
42 | notificationTableScreen.notificationRecylerView.clickAtPosition(2)
43 | intended(hasComponent(NotificationDetailActivity::class.java.name))
44 | assertThat(notificationDetailScreen.title.getText())
45 | .describedAs("Notification title")
46 | .isEqualTo("Dispute message")
47 | assertThat(notificationDetailScreen.message.getText())
48 | .describedAs("Notification message")
49 | .isEqualTo("You received a dispute message for trade with ID 34059340")
50 | assertThat(notificationDetailScreen.action.getText())
51 | .describedAs("Notification action")
52 | .isEqualTo("Please contact the arbitrator")
53 | assertThat(notificationDetailScreen.eventTime.getText())
54 | .describedAs("Notification event time")
55 | .matches("Event occurred: 20\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d")
56 | assertThat(notificationDetailScreen.receivedTime.getText())
57 | .describedAs("Notification received time")
58 | .matches("Event received: 20\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d")
59 | }
60 |
61 | @Test
62 | fun clickDeleteButtonDeletesNotification() {
63 | notificationTableActivityRule.launch()
64 |
65 | notificationTableScreen.addExampleNotificationsMenuItem.click()
66 | val countBeforeSwipe = notificationTableScreen.notificationRecylerView.getItemCount()
67 |
68 | notificationTableScreen.notificationRecylerView.clickAtPosition(0)
69 | intended(hasComponent(NotificationDetailActivity::class.java.name))
70 |
71 | notificationDetailScreen.deleteButton.click()
72 | val countAfterDelete = notificationTableScreen.notificationRecylerView.getItemCount()
73 | assertThat(countAfterDelete)
74 | .describedAs("Notification count after delete")
75 | .isEqualTo(countBeforeSwipe - 1)
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/pairing/PairingScanActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui.pairing
19 |
20 | import android.content.Intent
21 | import android.os.Handler
22 | import android.os.Looper
23 | import android.view.View
24 | import android.widget.Button
25 | import android.widget.ImageView
26 | import android.widget.TextView
27 | import android.widget.Toast
28 | import bisq.android.R
29 | import bisq.android.model.Device
30 | import bisq.android.model.DeviceStatus
31 | import bisq.android.ui.UnpairedBaseActivity
32 | import bisq.android.util.QrUtil
33 |
34 | class PairingScanActivity : UnpairedBaseActivity() {
35 | private lateinit var qrImage: ImageView
36 | private lateinit var qrPlaceholderText: TextView
37 | private lateinit var noWebcamButton: Button
38 | private lateinit var simulatePairingButton: Button
39 |
40 | private val mainHandler = Handler(Looper.getMainLooper())
41 |
42 | override fun getRootLayoutId() = R.id.pairing_scan_layout
43 | override fun getStatusBarScrimId() = R.id.pairing_scan_status_bar_background
44 |
45 | override fun initView() {
46 | setContentView(R.layout.activity_pairing_scan)
47 |
48 | qrImage = this.bind(R.id.pairing_scan_qr_image)
49 |
50 | qrPlaceholderText = this.bind(R.id.pairing_scan_qr_placeholder)
51 |
52 | noWebcamButton = bind(R.id.pairing_scan_no_webcam_button)
53 | noWebcamButton.setOnClickListener {
54 | onNoWebcam()
55 | }
56 |
57 | simulatePairingButton = bind(R.id.pairing_scan_simulate_pairing_button)
58 | if (Device.instance.isEmulator() && Device.instance.status != DeviceStatus.PAIRED) {
59 | simulatePairingButton.visibility = View.VISIBLE
60 | }
61 | simulatePairingButton.setOnClickListener {
62 | onSimulatePairing()
63 | }
64 |
65 | mainHandler.post {
66 | if (Device.instance.pairingToken() == null) {
67 | return@post
68 | }
69 | @Suppress("TooGenericExceptionCaught")
70 | try {
71 | val bmp = QrUtil.createQrImage(Device.instance.pairingToken()!!)
72 | qrImage.setImageBitmap(bmp)
73 | qrPlaceholderText.visibility = View.INVISIBLE
74 | } catch (ignored: Exception) {
75 | Toast.makeText(
76 | this,
77 | getString(R.string.cannot_generate_qr_code),
78 | Toast.LENGTH_LONG
79 | ).show()
80 | }
81 | }
82 | }
83 |
84 | private fun onNoWebcam() {
85 | startActivity(Intent(Intent(this, PairingSendActivity::class.java)))
86 | }
87 |
88 | private fun onSimulatePairing() {
89 | Device.instance.status = DeviceStatus.PAIRED
90 | Device.instance.saveToPreferences(this)
91 | pairingConfirmed()
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/rules/LazyActivityScenarioRule.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.rules
19 |
20 | import android.app.Activity
21 | import android.content.Intent
22 | import androidx.test.core.app.ActivityScenario
23 | import androidx.test.ext.junit.rules.ActivityScenarioRule
24 | import org.junit.rules.ExternalResource
25 |
26 | /**
27 | * Provides a drop-in replacement for [ActivityScenarioRule] that allows for
28 | * optional launching of the Activity on start.
29 | *
30 | * @get:Rule
31 | * val rule = lazyActivityScenarioRule(launchActivity = false)
32 | *
33 | * @Test
34 | * fun myTest() {
35 | * // do some setup
36 | * rule.launch()
37 | * // do some stuff with the Activity
38 | * }
39 | */
40 | class LazyActivityScenarioRule : ExternalResource {
41 | private var launchActivity: Boolean
42 |
43 | private var scenarioSupplier: () -> ActivityScenario
44 |
45 | private var scenario: ActivityScenario? = null
46 |
47 | private var scenarioLaunched: Boolean = false
48 |
49 | constructor(launchActivity: Boolean, startActivityIntentSupplier: () -> Intent) {
50 | this.launchActivity = launchActivity
51 | scenarioSupplier = { ActivityScenario.launch(startActivityIntentSupplier()) }
52 | }
53 |
54 | constructor(launchActivity: Boolean, startActivityIntent: Intent) {
55 | this.launchActivity = launchActivity
56 | scenarioSupplier = { ActivityScenario.launch(startActivityIntent) }
57 | }
58 |
59 | constructor(launchActivity: Boolean, startActivityClass: Class) {
60 | this.launchActivity = launchActivity
61 | scenarioSupplier = { ActivityScenario.launch(startActivityClass) }
62 | }
63 |
64 | override fun before() {
65 | if (launchActivity) {
66 | launch()
67 | }
68 | }
69 |
70 | override fun after() {
71 | scenario?.close()
72 | }
73 |
74 | fun launch(newIntent: Intent? = null) {
75 | if (!scenarioLaunched) {
76 | newIntent?.let { scenarioSupplier = { ActivityScenario.launch(it) } }
77 | scenario = scenarioSupplier()
78 | scenarioLaunched = true
79 | }
80 | }
81 |
82 | fun getScenario(): ActivityScenario = checkNotNull(scenario)
83 | }
84 |
85 | inline fun lazyActivityScenarioRule(
86 | launchActivity: Boolean = true,
87 | noinline intentSupplier: () -> Intent
88 | ): LazyActivityScenarioRule =
89 | LazyActivityScenarioRule(launchActivity, intentSupplier)
90 |
91 | inline fun lazyActivityScenarioRule(
92 | launchActivity: Boolean = true,
93 | intent: Intent? = null
94 | ): LazyActivityScenarioRule = if (intent == null) {
95 | LazyActivityScenarioRule(launchActivity, A::class.java)
96 | } else {
97 | LazyActivityScenarioRule(launchActivity, intent)
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/services/NotificationHandler.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.services
19 |
20 | import android.content.Context
21 | import android.content.Intent
22 | import bisq.android.Logging
23 | import bisq.android.R
24 | import bisq.android.database.BisqNotification
25 | import bisq.android.database.DebugLogRepository
26 | import bisq.android.database.NotificationRepository
27 | import bisq.android.model.Device
28 | import bisq.android.model.DeviceStatus
29 | import bisq.android.model.NotificationType
30 | import bisq.android.services.BisqFirebaseMessagingService.Companion.refreshFcmToken
31 |
32 | object NotificationHandler {
33 |
34 | private const val TAG = "NotificationHandler"
35 |
36 | @Suppress("ReturnCount")
37 | suspend fun handleNotification(bisqNotification: BisqNotification, context: Context) {
38 | val notificationRepository = NotificationRepository(context)
39 | val debugRepository = DebugLogRepository(context)
40 |
41 | when (bisqNotification.type) {
42 | NotificationType.SETUP_CONFIRMATION.name -> {
43 | Logging().debug(TAG, "Setup confirmation")
44 | if (Device.instance.token == null) {
45 | Logging().error(TAG, "Device token is null")
46 | return
47 | }
48 | if (Device.instance.key == null) {
49 | Logging().error(TAG, "Device key is null")
50 | return
51 | }
52 | if (Device.instance.status == DeviceStatus.PAIRED) {
53 | Logging().warn(TAG, "Device is already paired")
54 | return
55 | }
56 | Device.instance.status = DeviceStatus.PAIRED
57 | Device.instance.saveToPreferences(context)
58 | }
59 | NotificationType.ERASE.name -> {
60 | Logging().debug(TAG, "Erase pairing")
61 | Device.instance.reset()
62 | Device.instance.clearPreferences(context)
63 | notificationRepository.deleteAll()
64 | debugRepository.deleteAll()
65 | Device.instance.status = DeviceStatus.REMOTE_ERASED
66 | refreshFcmToken()
67 | }
68 |
69 | null -> {
70 | Logging().error(TAG, "Notification type is null: $bisqNotification")
71 | }
72 | else -> {
73 | Logging().debug(TAG, "Inserting ${bisqNotification.type} notification to repository")
74 | notificationRepository.insert(bisqNotification)
75 | }
76 | }
77 |
78 | Logging().debug(TAG, "Broadcasting " + context.getString(R.string.intent_receiver_action))
79 | Intent().also { broadcastIntent ->
80 | broadcastIntent.action = context.getString(R.string.intent_receiver_action)
81 | broadcastIntent.putExtra("type", bisqNotification.type)
82 | context.sendBroadcast(broadcastIntent)
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/pairing/RequestNotificationPermissionActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui.pairing
19 |
20 | import android.Manifest
21 | import android.content.Intent
22 | import android.os.Build
23 | import android.widget.Button
24 | import androidx.core.app.ActivityCompat
25 | import bisq.android.R
26 | import bisq.android.ui.PairedBaseActivity
27 | import bisq.android.ui.notification.NotificationTableActivity
28 |
29 | class RequestNotificationPermissionActivity : PairedBaseActivity() {
30 | companion object {
31 | private const val NOTIFICATION_PERMISSION_REQUEST_CODE = 1
32 | }
33 |
34 | private lateinit var requestNotificationPermissionButton: Button
35 | private lateinit var skipPermissionButton: Button
36 |
37 | override fun getRootLayoutId() = R.id.request_notification_permission_layout
38 | override fun getStatusBarScrimId() = R.id.request_notification_permission_status_bar_background
39 |
40 | override fun initView() {
41 | setContentView(R.layout.activity_request_notification_permission)
42 |
43 | requestNotificationPermissionButton = bind(R.id.request_notification_permission_button)
44 | requestNotificationPermissionButton.setOnClickListener {
45 | onRequestNotificationPermission()
46 | }
47 |
48 | skipPermissionButton = bind(R.id.skip_request_notification_permission_button)
49 | skipPermissionButton.setOnClickListener {
50 | val intent = Intent(this, NotificationTableActivity::class.java)
51 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
52 | startActivity(intent)
53 | }
54 | }
55 |
56 | private fun onRequestNotificationPermission() {
57 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
58 | ActivityCompat.requestPermissions(
59 | this,
60 | arrayOf(Manifest.permission.POST_NOTIFICATIONS),
61 | NOTIFICATION_PERMISSION_REQUEST_CODE
62 | )
63 | } else {
64 | val intent = Intent(this, NotificationTableActivity::class.java)
65 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
66 | startActivity(intent)
67 | }
68 | }
69 |
70 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
71 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
72 | when (requestCode) {
73 | NOTIFICATION_PERMISSION_REQUEST_CODE -> {
74 | // If request is cancelled, the result arrays are empty
75 | if (grantResults.isNotEmpty()) {
76 | val intent = Intent(this, NotificationTableActivity::class.java)
77 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
78 | startActivity(intent)
79 | }
80 | return
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_pairing_success.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
19 |
20 |
21 |
29 |
30 |
42 |
43 |
58 |
59 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/DialogBuilder.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui
19 |
20 | import android.content.Context
21 | import android.content.DialogInterface
22 | import android.graphics.Color
23 | import androidx.appcompat.app.AlertDialog
24 | import androidx.core.content.ContextCompat
25 | import bisq.android.R
26 |
27 | object DialogBuilder {
28 |
29 | @Suppress("LongParameterList")
30 | fun choicePrompt(
31 | context: Context,
32 | title: String,
33 | message: String,
34 | positiveButtonText: String,
35 | negativeButtonText: String,
36 | positiveActionListener: DialogInterface.OnClickListener,
37 | negativeActionListener: DialogInterface.OnClickListener? = null
38 | ): AlertDialog {
39 | val builder = AlertDialog.Builder(context)
40 | builder.setTitle(title)
41 | builder.setMessage(message)
42 | builder.setCancelable(true)
43 | builder.setPositiveButton(positiveButtonText, positiveActionListener)
44 | if (negativeActionListener != null) {
45 | builder.setNegativeButton(
46 | negativeButtonText,
47 | negativeActionListener
48 | )
49 | } else {
50 | builder.setNegativeButton(
51 | negativeButtonText
52 | ) { dialog, _ ->
53 | dialog.cancel()
54 | }
55 | }
56 |
57 | val alertDialog = builder.create()
58 |
59 | alertDialog.setOnShowListener { dialog ->
60 | val positiveButton = (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)
61 | positiveButton.setTextColor(ContextCompat.getColor(context, R.color.primary))
62 | val negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
63 | negativeButton.setTextColor(Color.RED)
64 | }
65 |
66 | return alertDialog
67 | }
68 |
69 | fun prompt(
70 | context: Context,
71 | title: String,
72 | message: String,
73 | buttonText: String,
74 | actionListener: DialogInterface.OnClickListener? = null
75 | ): AlertDialog {
76 | val builder = AlertDialog.Builder(context)
77 | builder.setTitle(title)
78 | builder.setMessage(message)
79 | builder.setCancelable(true)
80 | if (actionListener != null) {
81 | builder.setPositiveButton(
82 | buttonText,
83 | actionListener
84 | )
85 | } else {
86 | builder.setPositiveButton(
87 | buttonText
88 | ) { dialog, _ ->
89 | dialog.cancel()
90 | }
91 | }
92 |
93 | val alertDialog = builder.create()
94 |
95 | alertDialog.setOnShowListener { dialog ->
96 | val positiveButton = (dialog as AlertDialog).getButton(AlertDialog.BUTTON_POSITIVE)
97 | positiveButton.setTextColor(ContextCompat.getColor(context, R.color.primary))
98 | }
99 |
100 | return alertDialog
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/pairing/PairingSuccessActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui.pairing
19 |
20 | import android.Manifest
21 | import android.content.Intent
22 | import android.content.pm.PackageManager
23 | import android.os.Build
24 | import android.widget.Button
25 | import androidx.core.app.ActivityCompat
26 | import androidx.core.content.ContextCompat
27 | import bisq.android.R
28 | import bisq.android.ui.PairedBaseActivity
29 | import bisq.android.ui.notification.NotificationTableActivity
30 |
31 | class PairingSuccessActivity : PairedBaseActivity() {
32 | companion object {
33 | private const val NOTIFICATION_PERMISSION_REQUEST_CODE = 1
34 | }
35 |
36 | private lateinit var pairingCompleteButton: Button
37 |
38 | override fun getRootLayoutId() = R.id.pairing_success_layout
39 | override fun getStatusBarScrimId() = R.id.pairing_success_status_bar_background
40 |
41 | override fun initView() {
42 | setContentView(R.layout.activity_pairing_success)
43 |
44 | pairingCompleteButton = bind(R.id.pairing_scan_pairing_complete_button)
45 | pairingCompleteButton.setOnClickListener {
46 | onPairingComplete()
47 | }
48 | }
49 |
50 | private fun onPairingComplete() {
51 | when {
52 | ContextCompat.checkSelfPermission(
53 | this,
54 | Manifest.permission.POST_NOTIFICATIONS
55 | ) == PackageManager.PERMISSION_GRANTED -> {
56 | val intent = Intent(this, NotificationTableActivity::class.java)
57 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
58 | startActivity(intent)
59 | }
60 |
61 | ActivityCompat.shouldShowRequestPermissionRationale(
62 | this,
63 | Manifest.permission.POST_NOTIFICATIONS
64 | ) -> {
65 | startActivity(Intent(Intent(this, RequestNotificationPermissionActivity::class.java)))
66 | }
67 |
68 | else -> {
69 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
70 | requestPermissions(
71 | arrayOf(Manifest.permission.POST_NOTIFICATIONS),
72 | NOTIFICATION_PERMISSION_REQUEST_CODE
73 | )
74 | }
75 | }
76 | }
77 | }
78 |
79 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
80 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
81 | when (requestCode) {
82 | NOTIFICATION_PERMISSION_REQUEST_CODE -> {
83 | // If request is cancelled, the result arrays are empty
84 | if (grantResults.isNotEmpty()) {
85 | val intent = Intent(this, NotificationTableActivity::class.java)
86 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
87 | startActivity(intent)
88 | }
89 | return
90 | }
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/notification/NotificationDetailActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui.notification
19 |
20 | import android.os.Bundle
21 | import android.view.View
22 | import android.widget.Button
23 | import android.widget.TextView
24 | import androidx.lifecycle.ViewModelProvider
25 | import bisq.android.R
26 | import bisq.android.database.BisqNotification
27 | import bisq.android.ui.PairedBaseActivity
28 | import bisq.android.util.DateUtil
29 |
30 | class NotificationDetailActivity : PairedBaseActivity() {
31 |
32 | private lateinit var viewModel: NotificationViewModel
33 | private lateinit var title: TextView
34 | private lateinit var message: TextView
35 | private lateinit var action: TextView
36 | private lateinit var eventTime: TextView
37 | private lateinit var receivedTime: TextView
38 | private lateinit var deleteButton: Button
39 |
40 | override fun getRootLayoutId() = R.id.notification_detail_layout
41 | override fun getStatusBarScrimId() = R.id.notification_detail_status_bar_background
42 |
43 | override fun onCreate(savedInstanceState: Bundle?) {
44 | viewModel = ViewModelProvider(this)[NotificationViewModel::class.java]
45 |
46 | super.onCreate(savedInstanceState)
47 |
48 | val notification = getNotification() ?: return
49 |
50 | updateView(notification)
51 |
52 | viewModel.markAsRead(notification.uid)
53 | }
54 |
55 | private fun getNotification(): BisqNotification? {
56 | val uid = intent.getIntExtra("uid", 0)
57 | return viewModel.getFromUid(uid)
58 | }
59 |
60 | override fun initView() {
61 | setContentView(R.layout.activity_notification_detail)
62 | title = bind(R.id.notification_detail_title)
63 | message = bind(R.id.notification_detail_message)
64 | action = bind(R.id.notification_detail_action)
65 | eventTime = bind(R.id.notification_detail_event_time)
66 | receivedTime = bind(R.id.notification_detail_received_time)
67 | deleteButton = bind(R.id.notification_delete_button)
68 | deleteButton.setOnClickListener {
69 | getNotification()?.let { notification -> viewModel.delete(notification) }
70 | finish()
71 | }
72 | }
73 |
74 | private fun updateView(notification: BisqNotification) {
75 | title.text = notification.title
76 |
77 | if (notification.message != null && notification.message!!.isNotEmpty()) {
78 | message.text = notification.message
79 | } else {
80 | message.visibility = View.GONE
81 | }
82 |
83 | if (notification.actionRequired != null && notification.actionRequired!!.isNotEmpty()) {
84 | action.text = notification.actionRequired
85 | } else {
86 | action.visibility = View.GONE
87 | }
88 |
89 | eventTime.text = if (notification.sentDate > 0) {
90 | getString(R.string.event_occurred_at, DateUtil.format(notification.sentDate))
91 | } else {
92 | ""
93 | }
94 | receivedTime.text = if (notification.receivedDate > 0) {
95 | getString(R.string.event_received_at, DateUtil.format(notification.receivedDate))
96 | } else {
97 | ""
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/app/src/main/java/bisq/android/ui/debug/DebugActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.ui.debug
19 |
20 | import android.content.Intent
21 | import android.os.Bundle
22 | import android.widget.Button
23 | import android.widget.Switch
24 | import android.widget.TextView
25 | import androidx.lifecycle.ViewModelProvider
26 | import bisq.android.R
27 | import bisq.android.database.DebugLogLevel
28 | import bisq.android.model.Device
29 | import bisq.android.ui.BaseActivity
30 |
31 | class DebugActivity : BaseActivity() {
32 | private lateinit var viewModel: DebugViewModel
33 | private lateinit var deviceStatusText: TextView
34 | private lateinit var showDebugLogsLabel: TextView
35 | private lateinit var showDebugLogsSwitch: Switch
36 | private lateinit var logText: TextView
37 | private lateinit var clearLogButton: Button
38 | private lateinit var sendLogButton: Button
39 |
40 | private var showDebugLogs: Boolean = false
41 |
42 | override fun getRootLayoutId() = R.id.debug_layout
43 | override fun getStatusBarScrimId() = R.id.debug_status_bar_background
44 |
45 | override fun onCreate(savedInstanceState: Bundle?) {
46 | viewModel = ViewModelProvider(this)[DebugViewModel::class.java]
47 |
48 | super.onCreate(savedInstanceState)
49 | }
50 |
51 | override fun onStart() {
52 | super.onStart()
53 | viewModel.allLogs.observe(this) { _ ->
54 | updateView()
55 | }
56 | }
57 |
58 | override fun initView() {
59 | setContentView(R.layout.activity_debug)
60 |
61 | deviceStatusText = bind(R.id.device_status_value)
62 | deviceStatusText.text = Device.instance.status.toString()
63 |
64 | showDebugLogsSwitch = bind(R.id.show_debug_log_switch)
65 | showDebugLogsSwitch.setOnCheckedChangeListener { _, isChecked ->
66 | showDebugLogs = isChecked
67 | updateView()
68 | }
69 |
70 | showDebugLogsLabel = bind(R.id.show_debug_log_label)
71 | showDebugLogsLabel.setOnClickListener {
72 | showDebugLogsSwitch.isChecked = !showDebugLogsSwitch.isChecked
73 | }
74 |
75 | logText = bind(R.id.log_text)
76 |
77 | clearLogButton = bind(R.id.clear_log_button)
78 | clearLogButton.setOnClickListener {
79 | onClearLog()
80 | }
81 |
82 | sendLogButton = bind(R.id.send_log_button)
83 | sendLogButton.setOnClickListener {
84 | onSendLog()
85 | }
86 | }
87 |
88 | private fun updateView() {
89 | val allLogs = viewModel.allLogs.value ?: emptyList()
90 |
91 | val displayedLogs = if (!showDebugLogs) {
92 | allLogs.filter { log -> log.level != DebugLogLevel.DEBUG }
93 | } else {
94 | allLogs
95 | }
96 |
97 | logText.text = displayedLogs.joinToString(separator = "\n----------------------------------------------\n") {
98 | it.toString()
99 | }
100 | }
101 |
102 | private fun onClearLog() {
103 | viewModel.nukeTable()
104 | }
105 |
106 | private fun onSendLog() {
107 | val sendIntent: Intent = Intent().apply {
108 | action = Intent.ACTION_SEND
109 | type = "text/plain"
110 | putExtra(Intent.EXTRA_SUBJECT, getString(R.string.send_log_subject))
111 | putExtra(Intent.EXTRA_TEXT, logText.text)
112 | }
113 | startActivity(Intent.createChooser(sendIntent, getString(R.string.send_log)))
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/bisq/android/tests/RequestNotificationPermissionTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * This file is part of Bisq.
3 | *
4 | * Bisq is free software: you can redistribute it and/or modify it
5 | * under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or (at
7 | * your option) any later version.
8 | *
9 | * Bisq is distributed in the hope that it will be useful, but WITHOUT
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
12 | * License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with Bisq. If not, see .
16 | */
17 |
18 | package bisq.android.tests
19 |
20 | import android.Manifest
21 | import android.content.pm.PackageManager
22 | import android.os.Build
23 | import androidx.core.content.ContextCompat
24 | import androidx.test.espresso.intent.Intents.intended
25 | import androidx.test.espresso.intent.matcher.IntentMatchers
26 | import androidx.test.ext.junit.runners.AndroidJUnit4
27 | import bisq.android.ui.notification.NotificationTableActivity
28 | import org.assertj.core.api.Assertions.assertThat
29 | import org.hamcrest.Matchers
30 | import org.junit.Before
31 | import org.junit.Test
32 | import org.junit.runner.RunWith
33 |
34 | @RunWith(AndroidJUnit4::class)
35 | class RequestNotificationPermissionTest : BaseTest() {
36 | @Before
37 | override fun setup() {
38 | super.setup()
39 | pairDevice()
40 | }
41 |
42 | @Test
43 | fun clickEnableNotificationsButtonLoadsNotificationPermissionRequestPrompt() {
44 | assumeMinApiLevel(Build.VERSION_CODES.TIRAMISU)
45 |
46 | requestNotificationPermissionActivityRule.launch()
47 |
48 | requestNotificationPermissionScreen.enableNotificationsButton.click()
49 | val expectedIntent = Matchers.allOf(
50 | IntentMatchers.hasAction("android.content.pm.action.REQUEST_PERMISSIONS"),
51 | IntentMatchers.hasExtra(
52 | "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES",
53 | Matchers.hasItemInArray(Manifest.permission.POST_NOTIFICATIONS)
54 | )
55 | )
56 | intended(expectedIntent)
57 | assertThat(requestNotificationPermissionScreen.permissionPrompt.isDisplayed())
58 | .describedAs("Request notification permission prompt is displayed")
59 | .isTrue()
60 | }
61 |
62 | @Test
63 | fun acceptingNotificationPermissionRequestLoadsNotificationTableScreen() {
64 | assumeMinApiLevel(Build.VERSION_CODES.TIRAMISU)
65 |
66 | requestNotificationPermissionActivityRule.launch()
67 |
68 | requestNotificationPermissionScreen.enableNotificationsButton.click()
69 | assertThat(requestNotificationPermissionScreen.permissionPrompt.isDisplayed())
70 | .describedAs("Request notification permission prompt is displayed")
71 | .isTrue()
72 | requestNotificationPermissionScreen.permissionPrompt.grantPermission()
73 | intended(IntentMatchers.hasComponent(NotificationTableActivity::class.java.name))
74 | assertThat(
75 | ContextCompat.checkSelfPermission(
76 | applicationContext,
77 | Manifest.permission.POST_NOTIFICATIONS
78 | )
79 | )
80 | .describedAs("Post notifications permission")
81 | .isEqualTo(PackageManager.PERMISSION_GRANTED)
82 | }
83 |
84 | @Test
85 | fun clickSkipButtonLoadsNotificationTableScreen() {
86 | assumeMinApiLevel(Build.VERSION_CODES.TIRAMISU)
87 |
88 | requestNotificationPermissionActivityRule.launch()
89 |
90 | requestNotificationPermissionScreen.skipPermissionButton.click()
91 | intended(IntentMatchers.hasComponent(NotificationTableActivity::class.java.name))
92 |
93 | assertThat(requestNotificationPermissionScreen.permissionPrompt.isDisplayed())
94 | .describedAs("Request notification permission prompt is not displayed")
95 | .isFalse()
96 |
97 | assertThat(
98 | ContextCompat.checkSelfPermission(
99 | applicationContext,
100 | Manifest.permission.POST_NOTIFICATIONS
101 | )
102 | )
103 | .describedAs("Post notifications permission")
104 | .isEqualTo(PackageManager.PERMISSION_DENIED)
105 | }
106 | }
107 |
--------------------------------------------------------------------------------