├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── codeql.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle.kts ├── proguard-rules.pro ├── schemas │ └── de.davis.passwordmanager.database.KeyGoDatabase │ │ ├── 1.json │ │ ├── 2.json │ │ └── 3.json └── src │ ├── androidTest │ └── java │ │ └── de │ │ └── davis │ │ └── passwordmanager │ │ └── database │ │ ├── DatabaseMigrationTest.kt │ │ └── KeyGoDatabaseTest.kt │ ├── github │ ├── AndroidManifest.xml │ ├── java │ │ └── de │ │ │ └── davis │ │ │ └── passwordmanager │ │ │ ├── App.java │ │ │ ├── ui │ │ │ ├── MainActivity.java │ │ │ ├── settings │ │ │ │ ├── SettingsFragment.java │ │ │ │ └── VersionFragment.java │ │ │ └── viewmodels │ │ │ │ └── UpdaterViewModel.java │ │ │ ├── updater │ │ │ ├── Updater.java │ │ │ ├── downloader │ │ │ │ ├── DownloadService.java │ │ │ │ └── WebDownloadService.java │ │ │ ├── exception │ │ │ │ └── RateLimitException.java │ │ │ ├── installer │ │ │ │ └── InstallBroadcastReceiver.java │ │ │ └── version │ │ │ │ └── Release.java │ │ │ └── utils │ │ │ └── BackgroundUtil.java │ └── res │ │ ├── layout │ │ └── fragment_updater.xml │ │ ├── values-de │ │ └── updater.xml │ │ └── values │ │ └── updater.xml │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── 30k.txt │ ├── ic_launcher-playstore.png │ ├── java │ │ └── de │ │ │ └── davis │ │ │ └── passwordmanager │ │ │ ├── Keys.java │ │ │ ├── PasswordManagerApplication.java │ │ │ ├── backup │ │ │ ├── BackupOperation.kt │ │ │ ├── BackupResourceProvider.kt │ │ │ ├── BackupResult.kt │ │ │ ├── DataBackup.kt │ │ │ ├── PasswordProvider.kt │ │ │ ├── ProgressContext.kt │ │ │ ├── SecureDataBackup.kt │ │ │ ├── impl │ │ │ │ ├── AndroidBackupListener.kt │ │ │ │ ├── AndroidPasswordProvider.kt │ │ │ │ ├── CsvBackup.kt │ │ │ │ ├── KdbxBackup.kt │ │ │ │ └── UriBackupResourceProvider.kt │ │ │ └── listener │ │ │ │ └── BackupListener.kt │ │ │ ├── database │ │ │ ├── ElementType.kt │ │ │ ├── KeyGoDatabase.kt │ │ │ ├── SecureElementManager.kt │ │ │ ├── converter │ │ │ │ ├── Converters.java │ │ │ │ └── ElementTypeConverter.kt │ │ │ ├── daos │ │ │ │ └── SecureElementWithTagDao.kt │ │ │ ├── dtos │ │ │ │ ├── Item.kt │ │ │ │ ├── SecureElement.kt │ │ │ │ └── TagWithCount.kt │ │ │ ├── entities │ │ │ │ ├── SecureElementEntity.kt │ │ │ │ ├── Tag.kt │ │ │ │ ├── TagWithCountEntity.kt │ │ │ │ ├── Timestamps.kt │ │ │ │ ├── details │ │ │ │ │ ├── ElementDetail.java │ │ │ │ │ ├── creditcard │ │ │ │ │ │ ├── CreditCardDetails.java │ │ │ │ │ │ └── Name.java │ │ │ │ │ └── password │ │ │ │ │ │ ├── PasswordDetails.kt │ │ │ │ │ │ └── Strength.kt │ │ │ │ ├── junction │ │ │ │ │ └── SecureElementTagCrossRef.kt │ │ │ │ └── wrappers │ │ │ │ │ └── CombinedElement.kt │ │ │ └── migration │ │ │ │ ├── MigrationSpec1To2.kt │ │ │ │ └── MigrationSpec2To3.kt │ │ │ ├── dialog │ │ │ ├── BaseDialogBuilder.java │ │ │ ├── DeleteDialog.java │ │ │ ├── EditDialogBuilder.java │ │ │ └── LoadingDialog.java │ │ │ ├── filter │ │ │ └── Filter.kt │ │ │ ├── gson │ │ │ ├── ElementDetailTypeAdapter.java │ │ │ ├── annotations │ │ │ │ └── ExcludeFromGson.java │ │ │ └── strategies │ │ │ │ └── ExcludeAnnotationStrategy.java │ │ │ ├── ktx │ │ │ ├── LifecycleOwner.kt │ │ │ ├── Parcelable.kt │ │ │ └── String.kt │ │ │ ├── listeners │ │ │ ├── OnCreditCardEndIconClickListener.java │ │ │ ├── OnInformationChangedListener.java │ │ │ └── text │ │ │ │ ├── CreditCardNumberTextWatcher.java │ │ │ │ └── ExpiryDateTextWatcher.java │ │ │ ├── manager │ │ │ ├── ActivityResultManager.java │ │ │ └── CopyManager.java │ │ │ ├── nfc │ │ │ ├── NfcManager.java │ │ │ └── Provider.java │ │ │ ├── security │ │ │ ├── BiometricAuthentication.kt │ │ │ ├── Cryptography.java │ │ │ └── mainpassword │ │ │ │ ├── MainPassword.kt │ │ │ │ ├── MainPasswordManager.kt │ │ │ │ └── MainPasswordSerializer.kt │ │ │ ├── services │ │ │ └── autofill │ │ │ │ ├── AutoSaveHandler.kt │ │ │ │ ├── AutofillService.kt │ │ │ │ ├── NodeTraverse.kt │ │ │ │ ├── SaveActivity.kt │ │ │ │ ├── SelectionActivity.kt │ │ │ │ ├── builder │ │ │ │ ├── DatasetBuilder.kt │ │ │ │ ├── InlineDatasetBuilder.kt │ │ │ │ ├── InlinePresentationBuilder.kt │ │ │ │ ├── MenuDatasetBuilder.kt │ │ │ │ ├── SaveInfoBuilder.kt │ │ │ │ ├── SuggestedDatasetBuilder.kt │ │ │ │ └── TextProvider.kt │ │ │ │ ├── entities │ │ │ │ ├── AutofillField.kt │ │ │ │ ├── AutofillForm.kt │ │ │ │ ├── AutofillPair.kt │ │ │ │ ├── SaveField.kt │ │ │ │ ├── SaveForm.kt │ │ │ │ ├── TraverseNode.kt │ │ │ │ └── UserCredentialsType.kt │ │ │ │ └── extensions │ │ │ │ ├── ClientStateExt.kt │ │ │ │ ├── FillContextExt.kt │ │ │ │ ├── NodeExt.kt │ │ │ │ └── PendingIntentExt.kt │ │ │ ├── text │ │ │ └── method │ │ │ │ └── CreditCardNumberTransformationMethod.java │ │ │ ├── ui │ │ │ ├── BaseMainActivity.java │ │ │ ├── BottomSectionHandler.java │ │ │ ├── GridLayoutManager.kt │ │ │ ├── LayoutManager.kt │ │ │ ├── LinearLayoutManager.java │ │ │ ├── auth │ │ │ │ ├── AuthenticateFragment.kt │ │ │ │ ├── AuthenticationActivity.kt │ │ │ │ ├── AuthenticationRequest.kt │ │ │ │ ├── AuthenticationResult.kt │ │ │ │ ├── AuthenticationState.kt │ │ │ │ ├── AuthenticationViewModel.kt │ │ │ │ ├── BasicAuthenticationFragment.kt │ │ │ │ └── SetMainPasswordFragment.kt │ │ │ ├── backup │ │ │ │ └── BackupFragment.kt │ │ │ ├── callbacks │ │ │ │ ├── SearchViewBackPressedHandler.java │ │ │ │ └── SlidingBackPaneManager.java │ │ │ ├── dashboard │ │ │ │ ├── DashboardAdapter.kt │ │ │ │ ├── DashboardFragment.kt │ │ │ │ ├── DashboardViewModel.kt │ │ │ │ ├── SecureElementDiffCallback.kt │ │ │ │ ├── managers │ │ │ │ │ ├── AbsItemManager.kt │ │ │ │ │ ├── ElementItemManager.kt │ │ │ │ │ └── TagItemManager.kt │ │ │ │ ├── menuprovider │ │ │ │ │ └── DefaultElementMenuProvider.kt │ │ │ │ ├── selection │ │ │ │ │ ├── KeyProvider.java │ │ │ │ │ └── SecureElementDetailsLookup.java │ │ │ │ └── viewholders │ │ │ │ │ ├── BasicViewHolder.java │ │ │ │ │ └── SecureElementViewHolder.java │ │ │ ├── elements │ │ │ │ ├── CreateSecureElementActivity.java │ │ │ │ ├── NoElementFragment.java │ │ │ │ ├── SEBaseUi.java │ │ │ │ ├── SEViewActivity.java │ │ │ │ ├── SEViewFragment.java │ │ │ │ ├── ViewSecureElementFragment.java │ │ │ │ ├── creditcard │ │ │ │ │ ├── CreateCreditCardActivity.java │ │ │ │ │ └── ViewCreditCardFragment.java │ │ │ │ └── password │ │ │ │ │ ├── CreatePasswordActivity.java │ │ │ │ │ ├── GeneratePasswordActivity.java │ │ │ │ │ └── ViewPasswordFragment.java │ │ │ ├── highlights │ │ │ │ └── HighlightsFragment.java │ │ │ ├── settings │ │ │ │ ├── BaseSettingsFragment.java │ │ │ │ └── BaseVersionFragment.java │ │ │ ├── sync │ │ │ │ └── ImportActivity.kt │ │ │ ├── viewmodels │ │ │ │ ├── HighlightsViewModel.java │ │ │ │ └── ScrollingViewModel.java │ │ │ └── views │ │ │ │ ├── AddBottomSheet.java │ │ │ │ ├── AddButton.java │ │ │ │ ├── ChangePasswordPreference.kt │ │ │ │ ├── CheckableImageButton.java │ │ │ │ ├── FilterBottomSheet.kt │ │ │ │ ├── InformationView.java │ │ │ │ ├── OptionBottomSheet.kt │ │ │ │ ├── PasswordStrengthBar.java │ │ │ │ ├── TagView.kt │ │ │ │ ├── UpdaterPreference.java │ │ │ │ ├── VersionPreference.java │ │ │ │ └── copy │ │ │ │ └── CopyView.java │ │ │ ├── utils │ │ │ ├── AssetsUtil.java │ │ │ ├── BrowserUtil.java │ │ │ ├── CreditCardUtil.java │ │ │ ├── GeneratorUtil.java │ │ │ ├── KeyUtil.java │ │ │ ├── PreferenceUtil.java │ │ │ ├── ResUtil.java │ │ │ ├── TimeoutUtil.java │ │ │ ├── VersionUtil.java │ │ │ └── card │ │ │ │ ├── Card.kt │ │ │ │ ├── CardFactory.kt │ │ │ │ ├── CardType.kt │ │ │ │ ├── Formatter.kt │ │ │ │ └── algorithm │ │ │ │ └── LuhnAlgorithm.kt │ │ │ └── version │ │ │ ├── CurrentVersion.java │ │ │ └── Version.java │ ├── proto │ │ └── user_main_password.proto │ └── res │ │ ├── color │ │ └── chip_background_color.xml │ │ ├── drawable │ │ ├── baseline_bug_report_24.xml │ │ ├── baseline_filter_list_24.xml │ │ ├── baseline_settings_backup_restore_24.xml │ │ ├── baseline_star_24.xml │ │ ├── baseline_star_outline_24.xml │ │ ├── ic_baseline_build_24.xml │ │ ├── ic_baseline_calendar_month_24.xml │ │ ├── ic_baseline_check_24.xml │ │ ├── ic_baseline_check_circle_24.xml │ │ ├── ic_baseline_close_24.xml │ │ ├── ic_baseline_code_24.xml │ │ ├── ic_baseline_contactless_24.xml │ │ ├── ic_baseline_credit_card_24.xml │ │ ├── ic_baseline_dashboard_24.xml │ │ ├── ic_baseline_delete_24.xml │ │ ├── ic_baseline_edit_24.xml │ │ ├── ic_baseline_location_on_24.xml │ │ ├── ic_baseline_more_vert_24.xml │ │ ├── ic_baseline_numbers_24.xml │ │ ├── ic_baseline_open_in_new_24.xml │ │ ├── ic_baseline_password_24.xml │ │ ├── ic_baseline_person_24.xml │ │ ├── ic_baseline_refresh_24.xml │ │ ├── ic_baseline_security_24.xml │ │ ├── ic_baseline_settings_24.xml │ │ ├── ic_baseline_title_24.xml │ │ ├── password_eye.xml │ │ ├── pb_avd_hide_password.xml │ │ ├── pb_avd_show_password.xml │ │ ├── pb_design_ic_visibility.xml │ │ └── pb_design_ic_visibility_off.xml │ │ ├── layout-sw600dp │ │ └── generate_radio_btn_layout.xml │ │ ├── layout-w600dp │ │ └── activity_main.xml │ │ ├── layout │ │ ├── activity_authentication.xml │ │ ├── activity_create_creditcard.xml │ │ ├── activity_create_password.xml │ │ ├── activity_generate_password.xml │ │ ├── activity_import.xml │ │ ├── activity_main.xml │ │ ├── add_bottom_sheet_content.xml │ │ ├── app_bar_layout.xml │ │ ├── dialog_edit_view.xml │ │ ├── dialog_filter.xml │ │ ├── empty_fragment_container.xml │ │ ├── fav_layout.xml │ │ ├── fragment_authenticate.xml │ │ ├── fragment_dashboard.xml │ │ ├── fragment_highlights.xml │ │ ├── fragment_no_item_selected.xml │ │ ├── fragment_set_main_password.xml │ │ ├── fragment_updater.xml │ │ ├── fragment_view_credit_card.xml │ │ ├── fragment_view_password.xml │ │ ├── generate_radio_btn_layout.xml │ │ ├── information_view_content.xml │ │ ├── information_view_content_multi_line.xml │ │ ├── information_view_dialog_additional.xml │ │ ├── layout_autofill_menu.xml │ │ ├── layout_chip.xml │ │ ├── layout_element.xml │ │ ├── layout_element_letter_header.xml │ │ ├── layout_information_view.xml │ │ ├── layout_password_strength_bar.xml │ │ ├── layout_tag_item.xml │ │ ├── layout_tag_view.xml │ │ ├── list_pane.xml │ │ ├── loading_layout.xml │ │ ├── more_bottom_sheet_content.xml │ │ ├── navigation_rail_fab.xml │ │ ├── slider.xml │ │ ├── switch_layout.xml │ │ └── text_layout.xml │ │ ├── menu │ │ ├── bottom_navigation_menu.xml │ │ ├── continue_menu.xml │ │ ├── edit_menu.xml │ │ ├── generate_menu.xml │ │ └── view_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_background.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_background.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_background.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_background.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ ├── ic_launcher_background.webp │ │ ├── ic_launcher_foreground.webp │ │ └── ic_launcher_round.webp │ │ ├── navigation │ │ ├── element_nav_graph.xml │ │ └── nav_graph.xml │ │ ├── values-de │ │ └── strings.xml │ │ ├── values-night │ │ └── themes.xml │ │ ├── values │ │ ├── attrs.xml │ │ ├── attrs_information_view.xml │ │ ├── attrs_tag_view.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── preferences.xml │ │ ├── strings.xml │ │ ├── styles.xml │ │ └── themes.xml │ │ └── xml │ │ ├── autofill_configuration.xml │ │ ├── backup_preferences.xml │ │ ├── data_extraction_rules.xml │ │ └── root_preferences.xml │ ├── playstore │ └── java │ │ └── de │ │ └── davis │ │ └── passwordmanager │ │ └── ui │ │ ├── MainActivity.java │ │ └── settings │ │ ├── SettingsFragment.java │ │ └── VersionFragment.java │ └── test │ └── java │ └── de │ └── davis │ └── passwordmanager │ ├── utils │ └── BrowserUtilTest.kt │ └── version │ └── VersionTest.java ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [offrange] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: offrange # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ["btc.com/bc1qhdqymmss2tpc2rck3w6cgq4nvgcdwsv2v8vxwu", "etherscan.io/address/0x2838D4c9ABfC96B4f27a7281Db965078331da5E8"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help improve 4 | title: '' 5 | labels: bug 6 | assignees: OffRange 7 | 8 | --- 9 | 10 | **Description:** 11 | A clear and concise description of the bug you encountered. 12 | 13 | **Steps to Reproduce:** 14 | 1. Provide a step-by-step guide to reproduce the bug. 15 | 2. Be as detailed as possible, including any specific actions or inputs needed to trigger the bug. 16 | 3. Mention any relevant information about the environment (e.g., device, Android version, etc.). 17 | 18 | **Expected Behavior:** 19 | Describe what you expected to happen when performing the steps mentioned above. 20 | 21 | **Actual Behavior:** 22 | Explain what actually happened when performing the steps mentioned above. 23 | 24 | **Screenshots/Code Snippets:** 25 | If applicable, provide any relevant screenshots or code snippets that can help identify and understand the issue. 26 | 27 | **Additional Context:** 28 | Add any additional information that might be helpful for understanding the bug, such as error messages, logs, or related issues. 29 | 30 | **System Information:** 31 | - Device: [e.g., Google Pixel 3] 32 | - Android Version: [e.g., Android 10] 33 | - App Version: [e.g., 1.2.3] 34 | 35 | **Possible Solution (optional):** 36 | If you have any ideas or suggestions on how to fix the bug, please share them here. 37 | 38 | **Related Issues (optional):** 39 | If you know of any related issues or previously reported bugs, please list them here. 40 | 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: OffRange 7 | 8 | --- 9 | 10 | **Feature Request Template** 11 | 12 | **Description:** 13 | A clear and concise description of the feature you would like to request. 14 | 15 | **Problem/Use Case:** 16 | Explain the problem or use case that the feature aims to address. Provide context to help developers understand why this feature is needed or beneficial. 17 | 18 | **Proposed Solution:** 19 | Describe your proposed solution or idea for implementing the feature. Be as specific as possible, including any functionality, behavior, or user interface considerations. 20 | 21 | **Benefits:** 22 | Explain the potential benefits and advantages of implementing this feature. Highlight how it improves the user experience, adds value to the project, or addresses specific pain points. 23 | 24 | **Alternatives (optional):** 25 | If you have considered any alternatives or workarounds, please mention them here. This helps developers understand different approaches and evaluate the feasibility of the proposed feature. 26 | 27 | **Additional Context (optional):** 28 | Add any additional information or context that might be relevant to the feature request, such as related use cases, examples, or user feedback. 29 | 30 | **Note:** 31 | Please ensure that you have reviewed any existing project documentation, guidelines, or discussions related to feature requests before submitting this request. Also, be aware that the implementation of new features is subject to the project maintainers' discretion and roadmap. 32 | 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | ### JetBrains template 17 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 18 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 19 | 20 | .idea/ 21 | 22 | # User-specific stuff 23 | .idea/**/workspace.xml 24 | .idea/**/tasks.xml 25 | .idea/**/usage.statistics.xml 26 | .idea/**/dictionaries 27 | .idea/**/shelf 28 | 29 | # Generated files 30 | .idea/**/contentModel.xml 31 | 32 | # Sensitive or high-churn files 33 | .idea/**/dataSources/ 34 | .idea/**/dataSources.ids 35 | .idea/**/dataSources.local.xml 36 | .idea/**/sqlDataSources.xml 37 | .idea/**/dynamic.xml 38 | .idea/**/uiDesigner.xml 39 | .idea/**/dbnavigator.xml 40 | 41 | # Gradle 42 | .idea/**/gradle.xml 43 | .idea/**/libraries 44 | 45 | # Gradle and Maven with auto-import 46 | # When using Gradle or Maven with auto-import, you should exclude module files, 47 | # since they will be recreated, and may cause churn. Uncomment if using 48 | # auto-import. 49 | # .idea/artifacts 50 | # .idea/compiler.xml 51 | # .idea/jarRepositories.xml 52 | # .idea/modules.xml 53 | # .idea/*.iml 54 | # .idea/modules 55 | # *.iml 56 | # *.ipr 57 | 58 | # CMake 59 | cmake-build-*/ 60 | 61 | # Mongo Explorer plugin 62 | .idea/**/mongoSettings.xml 63 | 64 | # File-based project format 65 | *.iws 66 | 67 | # IntelliJ 68 | out/ 69 | 70 | # mpeltonen/sbt-idea plugin 71 | .idea_modules/ 72 | 73 | # JIRA plugin 74 | atlassian-ide-plugin.xml 75 | 76 | # Cursive Clojure plugin 77 | .idea/replstate.xml 78 | 79 | # Crashlytics plugin (for Android Studio and IntelliJ) 80 | com_crashlytics_export_strings.xml 81 | crashlytics.properties 82 | crashlytics-build.properties 83 | fabric.properties 84 | 85 | # Editor-based Rest Client 86 | .idea/httpRequests 87 | 88 | # Android studio 3.1+ serialized cache file 89 | .idea/caches/build_file_checksums.ser 90 | 91 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | ### Android template 2 | # Built application files 3 | *.apk 4 | *.aar 5 | *.ap_ 6 | *.aab 7 | 8 | /*/ 9 | !src/ 10 | !schemas/ 11 | 12 | 13 | # Files for the ART/Dalvik VM 14 | *.dex 15 | 16 | # Java class files 17 | *.class 18 | 19 | # Generated files 20 | bin/ 21 | gen/ 22 | out/ 23 | # Uncomment the following line in case you need and you don't have the release build type files in your app 24 | # release/ 25 | 26 | # Gradle files 27 | .gradle/ 28 | build/ 29 | 30 | # Local configuration file (sdk path, etc) 31 | local.properties 32 | 33 | # Proguard folder generated by Eclipse 34 | proguard/ 35 | 36 | # Log Files 37 | *.log 38 | 39 | # Android Studio Navigation editor temp files 40 | .navigation/ 41 | 42 | # Android Studio captures folder 43 | captures/ 44 | 45 | # IntelliJ 46 | *.iml 47 | .idea/workspace.xml 48 | .idea/tasks.xml 49 | .idea/gradle.xml 50 | .idea/assetWizardSettings.xml 51 | .idea/dictionaries 52 | .idea/libraries 53 | # Android Studio 3 in .gitignore file. 54 | .idea/caches 55 | .idea/modules.xml 56 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 57 | .idea/navEditor.xml 58 | 59 | # Keystore files 60 | # Uncomment the following lines if you do not want to check your keystore files in. 61 | #*.jks 62 | #*.keystore 63 | 64 | # External native build folder generated in Android Studio 2.2 and later 65 | .externalNativeBuild 66 | .cxx/ 67 | 68 | # Google Services (e.g. APIs or Firebase) 69 | # google-services.json 70 | 71 | # Freeline 72 | freeline.py 73 | freeline/ 74 | freeline_project_description.json 75 | 76 | # fastlane 77 | fastlane/report.xml 78 | fastlane/Preview.html 79 | fastlane/screenshots 80 | fastlane/test_output 81 | fastlane/readme.md 82 | 83 | # Version control 84 | vcs.xml 85 | 86 | # lint 87 | lint/intermediates/ 88 | lint/generated/ 89 | lint/outputs/ 90 | lint/tmp/ 91 | # lint/reports/ 92 | 93 | # Android Profiling 94 | *.hprof 95 | 96 | -------------------------------------------------------------------------------- /app/src/github/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/github/java/de/davis/passwordmanager/App.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager; 2 | 3 | import java.io.File; 4 | 5 | import de.davis.passwordmanager.updater.Updater; 6 | 7 | public class App extends PasswordManagerApplication{ 8 | 9 | private Updater updater; 10 | 11 | @Override 12 | public void onCreate() { 13 | super.onCreate(); 14 | updater = new Updater(this); 15 | } 16 | 17 | public Updater getUpdater() { 18 | return updater; 19 | } 20 | 21 | public File getDownloadDir(){ 22 | return new File(getCacheDir(), "apks/"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/github/java/de/davis/passwordmanager/ui/MainActivity.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui; 2 | 3 | import static de.davis.passwordmanager.utils.BackgroundUtil.doInBackground; 4 | 5 | import android.os.Bundle; 6 | 7 | import com.google.android.material.badge.BadgeDrawable; 8 | import com.google.android.material.navigation.NavigationBarView; 9 | 10 | import java.io.IOException; 11 | 12 | import de.davis.passwordmanager.App; 13 | import de.davis.passwordmanager.R; 14 | import de.davis.passwordmanager.updater.Updater; 15 | import de.davis.passwordmanager.updater.version.Release; 16 | import de.davis.passwordmanager.utils.PreferenceUtil; 17 | 18 | public class MainActivity extends BaseMainActivity { 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | Updater updater = ((App)getApplication()).getUpdater(); 24 | doInBackground(() -> { 25 | try { 26 | Release release = updater.fetchByChannel(PreferenceUtil.getUpdateChannel(this)); 27 | setBadge(release, findViewById(R.id.navigationView)); 28 | } catch (IOException ignore) {} 29 | }); 30 | } 31 | 32 | private void setBadge(Release release, NavigationBarView navigationBarView){ 33 | BadgeDrawable badgeDrawable = navigationBarView.getOrCreateBadge(R.id.settingsFragment); 34 | badgeDrawable.setVisible(release.isNewer()); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/github/java/de/davis/passwordmanager/ui/settings/SettingsFragment.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.settings; 2 | 3 | import static de.davis.passwordmanager.utils.BackgroundUtil.doInBackground; 4 | 5 | import android.os.Bundle; 6 | import android.os.Looper; 7 | 8 | import androidx.annotation.Nullable; 9 | import androidx.core.os.HandlerCompat; 10 | 11 | import java.io.IOException; 12 | 13 | import de.davis.passwordmanager.App; 14 | import de.davis.passwordmanager.R; 15 | import de.davis.passwordmanager.ui.views.VersionPreference; 16 | import de.davis.passwordmanager.updater.Updater; 17 | import de.davis.passwordmanager.updater.version.Release; 18 | import de.davis.passwordmanager.utils.PreferenceUtil; 19 | import de.davis.passwordmanager.version.CurrentVersion; 20 | 21 | public class SettingsFragment extends BaseSettingsFragment { 22 | 23 | @Override 24 | public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { 25 | super.onCreatePreferences(savedInstanceState, rootKey); 26 | 27 | findPreference(getString(R.string.version)).setIcon(R.drawable.ic_baseline_refresh_24); 28 | 29 | Updater updater = ((App)requireActivity().getApplication()).getUpdater(); 30 | doInBackground(() -> { 31 | try { 32 | Release cached = updater.fetchByChannel(PreferenceUtil.getUpdateChannel(requireContext())); 33 | boolean newer = cached.isNewer(); 34 | ((VersionPreference)findPreference(getString(R.string.version))).setHighlighted(newer); 35 | HandlerCompat.createAsync(Looper.getMainLooper()) 36 | .post(() -> findPreference(getString(R.string.version)) 37 | .setSummary(newer 38 | ? getString(R.string.newer_version_available, cached.getVersionTag()) 39 | : CurrentVersion.getInstance().getVersionTag())); 40 | } catch (IOException ignored) {} 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/github/java/de/davis/passwordmanager/ui/viewmodels/UpdaterViewModel.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.viewmodels; 2 | 3 | import static de.davis.passwordmanager.utils.BackgroundUtil.doInBackground; 4 | 5 | import android.app.Application; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.lifecycle.AndroidViewModel; 9 | import androidx.lifecycle.MutableLiveData; 10 | 11 | import java.io.IOException; 12 | 13 | import de.davis.passwordmanager.App; 14 | import de.davis.passwordmanager.updater.version.Release; 15 | import de.davis.passwordmanager.utils.PreferenceUtil; 16 | import de.davis.passwordmanager.version.Version; 17 | 18 | public class UpdaterViewModel extends AndroidViewModel { 19 | 20 | private boolean isAskingForPermission; 21 | private final MutableLiveData releaseLiveData = new MutableLiveData<>(); 22 | private final MutableLiveData errorLiveData = new MutableLiveData<>(); 23 | 24 | public UpdaterViewModel(@NonNull Application application) { 25 | super(application); 26 | fetchGitHubReleases(PreferenceUtil.getUpdateChannel(getApplication()), true); 27 | } 28 | 29 | public void fetchGitHubReleases(@Version.Channel int channel, boolean useCached) { 30 | doInBackground(() -> { 31 | try { 32 | releaseLiveData.postValue(((App)getApplication()) 33 | .getUpdater().fetchByChannel(channel, useCached)); 34 | } catch (IOException e) { 35 | errorLiveData.postValue(e); 36 | } 37 | }); 38 | } 39 | 40 | public MutableLiveData getReleaseLiveData() { 41 | return releaseLiveData; 42 | } 43 | 44 | public MutableLiveData getErrorLiveData() { 45 | return errorLiveData; 46 | } 47 | 48 | public void setAskingForPermission(boolean askingForPermission) { 49 | isAskingForPermission = askingForPermission; 50 | } 51 | 52 | public boolean isAskingForPermission() { 53 | return isAskingForPermission; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/github/java/de/davis/passwordmanager/updater/downloader/WebDownloadService.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.updater.downloader; 2 | 3 | import okhttp3.ResponseBody; 4 | import retrofit2.Call; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Path; 7 | import retrofit2.http.Streaming; 8 | 9 | public interface WebDownloadService { 10 | 11 | @GET("{vTag}/{assetName}/") 12 | @Streaming 13 | Call downloadRelease(@Path("vTag") String vTag, @Path("assetName") String assetName); 14 | } 15 | -------------------------------------------------------------------------------- /app/src/github/java/de/davis/passwordmanager/updater/exception/RateLimitException.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.updater.exception; 2 | 3 | import org.kohsuke.github.HttpException; 4 | import org.kohsuke.github.connector.GitHubConnectorResponse; 5 | 6 | import java.io.Serial; 7 | import java.util.Objects; 8 | 9 | public class RateLimitException extends HttpException { 10 | 11 | @Serial 12 | private static final long serialVersionUID = -4947605397389299267L; 13 | 14 | private final long reset; 15 | 16 | public RateLimitException(GitHubConnectorResponse connectorResponse) { 17 | super(connectorResponse); 18 | reset = Long.parseLong(Objects.requireNonNull(connectorResponse.header("X-Ratelimit-Reset"))); 19 | } 20 | 21 | public long getReset() { 22 | return reset; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/github/java/de/davis/passwordmanager/updater/installer/InstallBroadcastReceiver.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.updater.installer; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.pm.PackageInstaller; 7 | 8 | import androidx.localbroadcastmanager.content.LocalBroadcastManager; 9 | 10 | import java.io.File; 11 | 12 | public class InstallBroadcastReceiver extends BroadcastReceiver { 13 | 14 | public static final String ACTION_INSTALL = "de.davis.passwordmanager.action.INSTALL"; 15 | public static final String EXTRA_FILE = "de.davis.passwordmanager.extra.FILE"; 16 | 17 | @Override 18 | @SuppressWarnings("ResultOfMethodCallIgnored") 19 | public void onReceive(Context context, Intent intent) { 20 | int extraStatus = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -999); 21 | 22 | LocalBroadcastManager.getInstance(context).sendBroadcast(intent); 23 | 24 | if(extraStatus == PackageInstaller.STATUS_PENDING_USER_ACTION){ 25 | Intent startIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT); 26 | if(startIntent == null) 27 | return; 28 | 29 | startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 30 | 31 | context.startActivity(startIntent); 32 | }else if(extraStatus == PackageInstaller.STATUS_SUCCESS){ 33 | File apkFile = (File) intent.getSerializableExtra(EXTRA_FILE); 34 | if(apkFile != null) 35 | apkFile.delete(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/github/java/de/davis/passwordmanager/updater/version/Release.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.updater.version; 2 | 3 | import java.io.File; 4 | import java.io.Serial; 5 | 6 | import de.davis.passwordmanager.App; 7 | import de.davis.passwordmanager.BuildConfig; 8 | import de.davis.passwordmanager.version.Version; 9 | 10 | public class Release extends Version { 11 | 12 | @Serial 13 | private static final long serialVersionUID = -8619273636106428792L; 14 | private final String assetName; 15 | 16 | public Release(String assetName, String tagName) { 17 | super(fromVersionTag(tagName)); 18 | this.assetName = assetName; 19 | } 20 | 21 | public boolean isNewer(){ 22 | return getVersionCode() > BuildConfig.VERSION_CODE; 23 | } 24 | 25 | public File getDownloadedFile(App application) { 26 | return new File(application.getDownloadDir(), getVersionCode() + ".apk"); 27 | } 28 | 29 | public String getAssetName() { 30 | return assetName; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/github/java/de/davis/passwordmanager/utils/BackgroundUtil.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils; 2 | 3 | import java.util.concurrent.Executor; 4 | import java.util.concurrent.Executors; 5 | 6 | public class BackgroundUtil { 7 | 8 | private static final Executor executor = Executors.newSingleThreadExecutor(); 9 | 10 | public static void doInBackground(Runnable runnable){ 11 | executor.execute(runnable); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/github/res/values-de/updater.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Updater 4 | 5 | Update Kanal 6 | 7 | Suchen 8 | Herunterladen 9 | Installieren 10 | 11 | Diese Anwendung ist auf dem neuesten Stand 12 | Neuere Versionen werden gesucht 13 | Neuere version verfügbar: %1$s 14 | Neuere Version %1$s wird heruntergeladen 15 | Neue Version %1$s wird installiert 16 | Download initiieren 17 | Herrunterladen: %1$s%% 18 | 19 | 20 | GitHub-API-Limit überschritten 21 | Versuchen Sie in %1$d Sekunden erneut 22 | Ungültige APK. Versionscode oder Paketname stimmen nicht überein 23 | -------------------------------------------------------------------------------- /app/src/github/res/values/updater.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Updater 5 | 6 | Update Channel 7 | 8 | Scan 9 | Download 10 | Install 11 | 12 | You are up to date 13 | Scanning for newer versions 14 | An newer version is available: %1$s 15 | Downloading newer version %1$s 16 | Installing newer version %1$s 17 | Initiating download 18 | Downloading: %1$s%% 19 | 20 | GitHub API rate limit exceeded 21 | Try again in %1$d seconds 22 | 23 | Invalid APK. Version code or package mismatch 24 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OffRange/KeyGo/6f17141e00e5827885b4d3cd95c59e216ef3cb65/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/Keys.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager; 2 | 3 | public class Keys { 4 | 5 | public static final String KEY_OLD = "old"; 6 | public static final String KEY_NEW = "new"; 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/backup/BackupOperation.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.backup 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | 6 | @Parcelize 7 | enum class BackupOperation : Parcelable { 8 | IMPORT, 9 | EXPORT 10 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/backup/BackupResourceProvider.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.backup 2 | 3 | import java.io.InputStream 4 | import java.io.OutputStream 5 | 6 | interface BackupResourceProvider { 7 | 8 | suspend fun provideInputStream(): InputStream 9 | suspend fun provideOutputStream(): OutputStream 10 | 11 | suspend fun getFileName(): String 12 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/backup/BackupResult.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.backup 2 | 3 | sealed interface BackupResult { 4 | 5 | open class Success : BackupResult 6 | data class SuccessWithDuplicates(val duplicates: Int) : Success() 7 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/backup/DataBackup.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.backup 2 | 3 | import de.davis.passwordmanager.backup.listener.BackupListener 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.withContext 6 | import java.io.InputStream 7 | import java.io.OutputStream 8 | 9 | abstract class DataBackup(private val backupListener: BackupListener = BackupListener.Empty) { 10 | 11 | protected abstract suspend fun ProgressContext.runImport(inputStream: InputStream): BackupResult 12 | protected abstract suspend fun ProgressContext.runExport(outputStream: OutputStream): BackupResult 13 | 14 | private val progressContext = object : ProgressContext { 15 | override suspend fun initiateProgress(maxCount: Int) { 16 | withContext(Dispatchers.Main) { 17 | backupListener.initiateProgress(maxCount) 18 | } 19 | } 20 | 21 | override suspend fun madeProgress(progress: Int) { 22 | withContext(Dispatchers.Main) { 23 | backupListener.onProgressUpdated(progress) 24 | } 25 | } 26 | } 27 | 28 | open suspend fun execute( 29 | backupOperation: BackupOperation, 30 | backupResourceProvider: BackupResourceProvider 31 | ) { 32 | backupListener.run { 33 | runCatching { 34 | withContext(Dispatchers.Main) { onStart(backupOperation) } 35 | 36 | backupResourceProvider.run { 37 | progressContext.run { 38 | when (backupOperation) { 39 | BackupOperation.IMPORT -> provideInputStream().use { runImport(it) } 40 | BackupOperation.EXPORT -> provideOutputStream().use { runExport(it) } 41 | } 42 | } 43 | } 44 | 45 | }.onSuccess { 46 | withContext(Dispatchers.Main) { onSuccess(backupOperation, it) } 47 | }.onFailure { 48 | withContext(Dispatchers.Main) { onFailure(backupOperation, it) } 49 | } 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/backup/PasswordProvider.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.backup 2 | 3 | interface PasswordProvider { 4 | 5 | suspend operator fun invoke( 6 | backupOperation: BackupOperation, 7 | backupResourceProvider: BackupResourceProvider, 8 | callback: suspend (password: String) -> Unit 9 | ) 10 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/backup/ProgressContext.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.backup 2 | 3 | interface ProgressContext { 4 | suspend fun initiateProgress(maxCount: Int) 5 | suspend fun madeProgress(progress: Int) 6 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/backup/SecureDataBackup.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.backup 2 | 3 | import de.davis.passwordmanager.backup.listener.BackupListener 4 | import java.io.InputStream 5 | import java.io.OutputStream 6 | 7 | abstract class SecureDataBackup( 8 | private val passwordProvider: PasswordProvider, 9 | backupListener: BackupListener = BackupListener.Empty 10 | ) : DataBackup(backupListener) { 11 | 12 | private lateinit var password: String 13 | 14 | protected abstract suspend fun ProgressContext.runImport( 15 | inputStream: InputStream, 16 | password: String 17 | ): BackupResult 18 | 19 | protected abstract suspend fun ProgressContext.runExport( 20 | outputStream: OutputStream, 21 | password: String 22 | ): BackupResult 23 | 24 | final override suspend fun ProgressContext.runImport(inputStream: InputStream): BackupResult = 25 | runImport(inputStream, password) 26 | 27 | final override suspend fun ProgressContext.runExport(outputStream: OutputStream): BackupResult = 28 | runExport(outputStream, password) 29 | 30 | override suspend fun execute( 31 | backupOperation: BackupOperation, 32 | backupResourceProvider: BackupResourceProvider 33 | ) { 34 | passwordProvider(backupOperation, backupResourceProvider) { 35 | password = it 36 | super.execute(backupOperation, backupResourceProvider) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/backup/impl/UriBackupResourceProvider.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.backup.impl 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.ContentResolver 5 | import android.database.Cursor 6 | import android.net.Uri 7 | import android.provider.OpenableColumns 8 | import de.davis.passwordmanager.backup.BackupResourceProvider 9 | import kotlinx.coroutines.Dispatchers 10 | import kotlinx.coroutines.withContext 11 | import java.io.InputStream 12 | import java.io.OutputStream 13 | 14 | class UriBackupResourceProvider( 15 | private var uri: Uri, 16 | private var contentResolver: ContentResolver 17 | ) : BackupResourceProvider { 18 | 19 | override suspend fun provideInputStream(): InputStream = contentResolver.openInputStream(uri) 20 | ?: throw IllegalStateException("ContentResolver returned null") 21 | 22 | override suspend fun provideOutputStream(): OutputStream = contentResolver.openOutputStream(uri) 23 | ?: throw IllegalStateException("ContentResolver returned null") 24 | 25 | override suspend fun getFileName(): String = contentResolver.getFileName(uri) ?: "Unknown" 26 | 27 | private suspend fun ContentResolver.getFileName(uri: Uri): String? = 28 | withContext(Dispatchers.IO) { 29 | var fileName: String? = null 30 | if (uri.scheme == ContentResolver.SCHEME_CONTENT) { 31 | val cursor: Cursor? = query(uri, null, null, null, null) 32 | cursor?.use { 33 | if (it.moveToFirst()) { 34 | @SuppressLint("Range") 35 | fileName = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) 36 | } 37 | } 38 | } else if (uri.scheme == ContentResolver.SCHEME_FILE) { 39 | fileName = uri.lastPathSegment 40 | } 41 | 42 | fileName 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/backup/listener/BackupListener.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.backup.listener 2 | 3 | import de.davis.passwordmanager.backup.BackupOperation 4 | import de.davis.passwordmanager.backup.BackupResult 5 | 6 | interface BackupListener { 7 | fun onStart(backupOperation: BackupOperation) 8 | fun onSuccess(backupOperation: BackupOperation, backupResult: BackupResult) 9 | fun onFailure(backupOperation: BackupOperation, throwable: Throwable) 10 | 11 | fun initiateProgress(maxCount: Int) {} 12 | 13 | fun onProgressUpdated(progress: Int) {} 14 | 15 | data object Empty : BackupListener { 16 | override fun onStart(backupOperation: BackupOperation) {} 17 | override fun onSuccess(backupOperation: BackupOperation, backupResult: BackupResult) {} 18 | override fun onFailure(backupOperation: BackupOperation, throwable: Throwable) {} 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/ElementType.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database 2 | 3 | import androidx.annotation.DrawableRes 4 | import androidx.annotation.IdRes 5 | import androidx.annotation.StringRes 6 | import de.davis.passwordmanager.R 7 | import de.davis.passwordmanager.database.entities.Tag 8 | import de.davis.passwordmanager.database.entities.details.ElementDetail 9 | import de.davis.passwordmanager.database.entities.details.creditcard.CreditCardDetails 10 | import de.davis.passwordmanager.database.entities.details.password.PasswordDetails 11 | import de.davis.passwordmanager.ui.elements.CreateSecureElementActivity 12 | import de.davis.passwordmanager.ui.elements.creditcard.CreateCreditCardActivity 13 | import de.davis.passwordmanager.ui.elements.creditcard.ViewCreditCardFragment 14 | import de.davis.passwordmanager.ui.elements.password.CreatePasswordActivity 15 | import de.davis.passwordmanager.ui.elements.password.ViewPasswordFragment 16 | 17 | const val TAG_PREFIX: String = "elementType:" 18 | 19 | enum class ElementType( 20 | val typeId: Int, 21 | val elementDetailClass: Class, 22 | val createActivityClass: Class, 23 | @IdRes val viewFragmentId: Int, 24 | @StringRes var title: Int, 25 | @DrawableRes val icon: Int, 26 | var tag: Tag 27 | ) { 28 | PASSWORD( 29 | 0x1, 30 | PasswordDetails::class.java, 31 | CreatePasswordActivity::class.java, 32 | ViewPasswordFragment.ID, 33 | R.string.password, 34 | R.drawable.ic_baseline_password_24, 35 | Tag("${TAG_PREFIX}password") 36 | ), 37 | CREDIT_CARD( 38 | 0x11, 39 | CreditCardDetails::class.java, 40 | CreateCreditCardActivity::class.java, 41 | ViewCreditCardFragment.ID, 42 | R.string.credit_card, 43 | R.drawable.ic_baseline_credit_card_24, 44 | Tag("${TAG_PREFIX}credit_card") 45 | ); 46 | 47 | companion object { 48 | @JvmStatic 49 | fun getTypeByTypeId(id: Int): ElementType { 50 | return entries.first { it.typeId == id } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/KeyGoDatabase.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database 2 | 3 | import androidx.room.AutoMigration 4 | import androidx.room.Database 5 | import androidx.room.Room.databaseBuilder 6 | import androidx.room.RoomDatabase 7 | import androidx.room.TypeConverters 8 | import de.davis.passwordmanager.PasswordManagerApplication 9 | import de.davis.passwordmanager.database.converter.Converters 10 | import de.davis.passwordmanager.database.converter.ElementTypeConverter 11 | import de.davis.passwordmanager.database.daos.SecureElementWithTagDao 12 | import de.davis.passwordmanager.database.entities.SecureElementEntity 13 | import de.davis.passwordmanager.database.entities.Tag 14 | import de.davis.passwordmanager.database.entities.junction.SecureElementTagCrossRef 15 | import de.davis.passwordmanager.database.migration.MigrationSpec1To2 16 | import de.davis.passwordmanager.database.migration.MigrationSpec2To3 17 | 18 | @TypeConverters(Converters::class, ElementTypeConverter::class) 19 | @Database( 20 | version = 3, 21 | entities = [SecureElementEntity::class, Tag::class, SecureElementTagCrossRef::class], 22 | autoMigrations = [ 23 | AutoMigration(from = 1, to = 2, spec = MigrationSpec1To2::class), 24 | AutoMigration(from = 2, to = 3, spec = MigrationSpec2To3::class) 25 | ] 26 | ) 27 | abstract class KeyGoDatabase : RoomDatabase() { 28 | 29 | abstract fun combinedDao(): SecureElementWithTagDao 30 | 31 | companion object { 32 | private var INSTANCE: KeyGoDatabase? = null 33 | 34 | @JvmStatic 35 | val instance 36 | get() = INSTANCE ?: databaseBuilder( 37 | PasswordManagerApplication.getAppContext(), 38 | KeyGoDatabase::class.java, 39 | DB_NAME 40 | ).fallbackToDestructiveMigration().build() 41 | .also { INSTANCE = it } 42 | } 43 | } 44 | 45 | const val DB_NAME = "secure_element_database" -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/converter/Converters.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.converter; 2 | 3 | import androidx.annotation.Nullable; 4 | import androidx.room.TypeConverter; 5 | 6 | import com.google.gson.Gson; 7 | import com.google.gson.GsonBuilder; 8 | 9 | import java.util.Date; 10 | 11 | import de.davis.passwordmanager.database.entities.details.ElementDetail; 12 | import de.davis.passwordmanager.gson.ElementDetailTypeAdapter; 13 | import de.davis.passwordmanager.security.Cryptography; 14 | 15 | public class Converters { 16 | 17 | private static final Gson gson = new GsonBuilder().registerTypeAdapter(ElementDetail.class, new ElementDetailTypeAdapter()).create(); 18 | 19 | @TypeConverter 20 | @Nullable 21 | public static byte[] convertDetails(ElementDetail elementDetail){ 22 | String json = gson.toJson(elementDetail, ElementDetail.class); 23 | return Cryptography.encryptAES(json.getBytes()); 24 | } 25 | 26 | @TypeConverter 27 | public static ElementDetail convertByteArray(byte[] data){ 28 | byte[] decrypted = Cryptography.decryptAES(data); 29 | return gson.fromJson(new String(decrypted), ElementDetail.class); 30 | } 31 | 32 | @TypeConverter 33 | @Nullable 34 | public static Date fromTimestamp(Long value) { 35 | return value == null ? null : new Date(value); 36 | } 37 | 38 | @TypeConverter 39 | @Nullable 40 | public static Long dateToTimestamp(Date date) { 41 | return date == null ? null : date.getTime(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/converter/ElementTypeConverter.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.converter 2 | 3 | import androidx.room.TypeConverter 4 | import de.davis.passwordmanager.database.ElementType 5 | 6 | object ElementTypeConverter { 7 | 8 | @TypeConverter 9 | fun elementTypeToInt(elementType: ElementType): Int = elementType.typeId 10 | 11 | @TypeConverter 12 | fun intToElementType(elementId: Int): ElementType = ElementType.getTypeByTypeId(elementId) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/dtos/Item.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.dtos 2 | 3 | interface Item { 4 | val id: Long 5 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/dtos/TagWithCount.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.dtos 2 | 3 | import de.davis.passwordmanager.database.entities.Tag 4 | import de.davis.passwordmanager.database.entities.TagWithCountEntity 5 | 6 | data class TagWithCount(val tag: Tag, val count: Int) : Item { 7 | 8 | override val id: Long 9 | get() = tag.tagId 10 | } 11 | 12 | fun TagWithCountEntity.toDto() = TagWithCount(tag, count) 13 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/entities/SecureElementEntity.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Embedded 5 | import androidx.room.Entity 6 | import androidx.room.PrimaryKey 7 | import de.davis.passwordmanager.database.ElementType 8 | import de.davis.passwordmanager.database.entities.details.ElementDetail 9 | 10 | @Entity(tableName = "SecureElement") 11 | data class SecureElementEntity @JvmOverloads constructor( 12 | val title: String, 13 | @ColumnInfo(name = "data") val detail: ElementDetail, 14 | val favorite: Boolean = false, 15 | @Embedded val timestamps: Timestamps = Timestamps.CURRENT, 16 | @PrimaryKey(autoGenerate = true) var id: Long = 0, 17 | ) { 18 | var type: ElementType = detail.elementType 19 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/entities/Tag.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.entities 2 | 3 | import android.content.Context 4 | import androidx.room.Entity 5 | import androidx.room.Index 6 | import androidx.room.PrimaryKey 7 | import de.davis.passwordmanager.database.ElementType 8 | import de.davis.passwordmanager.database.TAG_PREFIX 9 | import de.davis.passwordmanager.gson.annotations.ExcludeFromGson 10 | 11 | @Entity(indices = [Index("name", unique = true)]) 12 | data class Tag @JvmOverloads constructor( 13 | val name: String, 14 | @ExcludeFromGson @PrimaryKey(autoGenerate = true) val tagId: Long = 0 15 | ) 16 | 17 | fun Collection.onlyCustoms(): Collection { 18 | return filter { !it.shouldBeProtected } 19 | } 20 | 21 | val Tag.shouldBeProtected get() = this.name.startsWith(TAG_PREFIX) 22 | 23 | val CharSequence.isProtectedTagName get() = startsWith(TAG_PREFIX) 24 | 25 | fun Tag.getLocalizedName(context: Context) = tryGetElementType()?.let { 26 | context.getString(it.title) 27 | } ?: name 28 | 29 | fun Tag.tryGetElementType() = 30 | if (shouldBeProtected) ElementType.entries.first { e -> e.tag.name == name } else null -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/entities/TagWithCountEntity.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.entities 2 | 3 | import androidx.room.Embedded 4 | 5 | data class TagWithCountEntity(@Embedded val tag: Tag, val count: Int) -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/entities/Timestamps.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.entities 2 | 3 | import androidx.room.ColumnInfo 4 | import java.util.Date 5 | 6 | data class Timestamps( 7 | @ColumnInfo(name = "created_at", defaultValue = "CURRENT_TIMESTAMP") var createdAt: Date?, 8 | @ColumnInfo(name = "modified_at") var modifiedAt: Date? = null 9 | ) { 10 | 11 | init { 12 | if (createdAt == null) 13 | createdAt = Date() 14 | } 15 | 16 | companion object { 17 | val CURRENT get() = Timestamps(Date()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/entities/details/ElementDetail.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.entities.details; 2 | 3 | import java.io.Serializable; 4 | 5 | import de.davis.passwordmanager.database.ElementType; 6 | 7 | public interface ElementDetail extends Serializable { 8 | 9 | ElementType getElementType(); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/entities/details/creditcard/Name.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.entities.details.creditcard; 2 | 3 | import java.io.Serial; 4 | import java.io.Serializable; 5 | import java.util.Objects; 6 | 7 | public class Name implements Serializable { 8 | 9 | @Serial 10 | private static final long serialVersionUID = 7569988556632962328L; 11 | 12 | private String lastName; 13 | private String firstName; 14 | 15 | public Name(String lastName, String firstName) { 16 | this.lastName = lastName; 17 | this.firstName = firstName; 18 | } 19 | 20 | public String getFirstName() { 21 | return firstName; 22 | } 23 | 24 | public String getLastName() { 25 | return lastName; 26 | } 27 | 28 | public void setFirstName(String firstName) { 29 | this.firstName = firstName; 30 | } 31 | 32 | public void setLastName(String lastName) { 33 | this.lastName = lastName; 34 | } 35 | 36 | public String getFullName(){ 37 | if(getFirstName() == null || getLastName() == null) 38 | return null; 39 | 40 | return getFirstName() +" "+ getLastName(); 41 | } 42 | 43 | public static Name fromFullName(String fullName){ 44 | return new Name(fullName.contains(" ") ? fullName.substring(0, fullName.lastIndexOf(" ")): "", 45 | fullName.contains(" ") ? fullName.substring(fullName.lastIndexOf(" ")+1) : ""); 46 | } 47 | 48 | @Override 49 | public boolean equals(Object o) { 50 | if (this == o) return true; 51 | if (!(o instanceof Name name)) return false; 52 | return Objects.equals(lastName, name.lastName) && Objects.equals(firstName, name.firstName); 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | return Objects.hash(lastName, firstName); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/entities/details/password/PasswordDetails.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.entities.details.password 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import de.davis.passwordmanager.database.ElementType 5 | import de.davis.passwordmanager.database.entities.details.ElementDetail 6 | import de.davis.passwordmanager.security.Cryptography 7 | import java.io.Serial 8 | 9 | class PasswordDetails( 10 | password: String, 11 | var origin: String, 12 | var username: String, 13 | ) : ElementDetail { 14 | 15 | var strength: Strength = EstimationHandler.estimate(password) 16 | private set 17 | 18 | @SerializedName("password") 19 | private var passwordEncrypted: ByteArray = Cryptography.encryptAES(password.toByteArray()) 20 | 21 | fun setPassword(password: String) { 22 | strength = EstimationHandler.estimate(password) 23 | passwordEncrypted = Cryptography.encryptAES(password.toByteArray()) 24 | } 25 | 26 | val password get() = String(Cryptography.decryptAES(passwordEncrypted)) 27 | 28 | 29 | override fun getElementType(): ElementType { 30 | return ElementType.PASSWORD 31 | } 32 | 33 | override fun equals(other: Any?): Boolean { 34 | if (this === other) return true 35 | if (javaClass != other?.javaClass) return false 36 | 37 | other as PasswordDetails 38 | 39 | if (origin != other.origin) return false 40 | if (username != other.username) return false 41 | return password == other.password 42 | } 43 | 44 | override fun hashCode(): Int { 45 | var result = origin.hashCode() 46 | result = 31 * result + username.hashCode() 47 | result = 31 * result + passwordEncrypted.contentHashCode() 48 | return result 49 | } 50 | 51 | 52 | companion object { 53 | @Serial 54 | private val serialVersionUID = 4938873580704485021L 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/entities/details/password/Strength.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.entities.details.password 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import androidx.annotation.AttrRes 6 | import androidx.annotation.ColorRes 7 | import com.google.android.material.color.MaterialColors 8 | import de.davis.passwordmanager.R 9 | import me.gosimple.nbvcxz.Nbvcxz 10 | import java.io.Serializable 11 | 12 | enum class Strength(@ColorRes val string: Int, @AttrRes val color: Int) : Serializable { 13 | 14 | RIDICULOUS(R.string.ridiculous, R.attr.colorRidiculous), 15 | WEAK(R.string.weak, R.attr.colorWeak), 16 | MODERATE(R.string.moderate, R.attr.colorModerate), 17 | STRONG(R.string.strong, R.attr.colorStrong), 18 | VERY_STRONG(R.string.very_strong, R.attr.colorVeryStrong); 19 | 20 | 21 | fun getColor(context: Context): Int { 22 | return MaterialColors.getColor(context, color, Color.BLACK) 23 | } 24 | } 25 | 26 | object EstimationHandler { 27 | 28 | private val nbvcxz = Nbvcxz() 29 | 30 | @JvmStatic 31 | fun estimate(password: String): Strength { 32 | val result = nbvcxz.estimate(password) 33 | return Strength.entries[result.basicScore] 34 | } 35 | 36 | @JvmStatic 37 | fun estimateWrapper(password: String): Wrapper { 38 | val result = nbvcxz.estimate(password) 39 | return Wrapper(Strength.entries[result.basicScore], result.feedback.warning) 40 | } 41 | } 42 | 43 | class Wrapper(val strength: Strength, val warning: String?) -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/entities/junction/SecureElementTagCrossRef.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.entities.junction 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | import androidx.room.ForeignKey.Companion.CASCADE 7 | import de.davis.passwordmanager.database.entities.SecureElementEntity 8 | import de.davis.passwordmanager.database.entities.Tag 9 | 10 | @Entity( 11 | primaryKeys = ["id", "tagId"], foreignKeys = [ 12 | ForeignKey( 13 | entity = SecureElementEntity::class, 14 | parentColumns = ["id"], 15 | childColumns = ["id"], 16 | onDelete = CASCADE, 17 | onUpdate = CASCADE 18 | ), 19 | ForeignKey( 20 | entity = Tag::class, 21 | parentColumns = ["tagId"], 22 | childColumns = ["tagId"], 23 | onDelete = CASCADE, 24 | onUpdate = CASCADE 25 | ) 26 | ] 27 | ) 28 | data class SecureElementTagCrossRef( 29 | @ColumnInfo(index = true) val id: Long, 30 | @ColumnInfo(index = true) val tagId: Long 31 | ) -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/entities/wrappers/CombinedElement.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.entities.wrappers 2 | 3 | import androidx.room.Embedded 4 | import androidx.room.Junction 5 | import androidx.room.Relation 6 | import de.davis.passwordmanager.database.entities.SecureElementEntity 7 | import de.davis.passwordmanager.database.entities.Tag 8 | import de.davis.passwordmanager.database.entities.junction.SecureElementTagCrossRef 9 | 10 | data class CombinedElement( 11 | @Embedded val secureElementEntity: SecureElementEntity, 12 | @Relation( 13 | parentColumn = "id", 14 | entityColumn = "tagId", 15 | associateBy = Junction(SecureElementTagCrossRef::class) 16 | ) 17 | val tags: List 18 | ) 19 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/database/migration/MigrationSpec1To2.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.database.migration 2 | 3 | import android.content.ContentValues 4 | import android.database.sqlite.SQLiteDatabase 5 | import androidx.room.migration.AutoMigrationSpec 6 | import androidx.sqlite.db.SupportSQLiteDatabase 7 | 8 | class MigrationSpec1To2 : AutoMigrationSpec { 9 | 10 | override fun onPostMigrate(db: SupportSQLiteDatabase) { 11 | val cv = ContentValues().apply { 12 | put("created_at", System.currentTimeMillis()) 13 | } 14 | db.update( 15 | "SecureElement", 16 | SQLiteDatabase.CONFLICT_REPLACE, 17 | cv, 18 | "created_at is ?", 19 | arrayOf(null) 20 | ) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/dialog/DeleteDialog.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.dialog; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | import com.google.android.material.dialog.MaterialAlertDialogBuilder; 7 | 8 | import java.util.List; 9 | 10 | import de.davis.passwordmanager.R; 11 | import de.davis.passwordmanager.database.SecureElementManager; 12 | import de.davis.passwordmanager.database.dtos.Item; 13 | 14 | public class DeleteDialog { 15 | 16 | private final MaterialAlertDialogBuilder builder; 17 | 18 | public DeleteDialog(Context context) { 19 | this.builder = new MaterialAlertDialogBuilder(context) 20 | .setTitle(R.string.permanently_delete) 21 | .setMessage(R.string.sure_delete); 22 | } 23 | 24 | public void show(List toDelete){ // if toDelete is null -> delete selected 25 | builder.setNegativeButton(R.string.no, (dialog, which) -> {}) 26 | .setPositiveButton(R.string.yes, (dialog, which) -> { 27 | SecureElementManager.delete(toDelete); 28 | 29 | Toast.makeText(builder.getContext(), R.string.successful_deleted, Toast.LENGTH_LONG).show(); 30 | }).show(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/dialog/LoadingDialog.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.dialog; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.appcompat.app.AlertDialog; 9 | 10 | import de.davis.passwordmanager.databinding.LoadingLayoutBinding; 11 | 12 | public class LoadingDialog extends BaseDialogBuilder { 13 | 14 | private LoadingLayoutBinding binding; 15 | private AlertDialog alertDialog; 16 | 17 | public LoadingDialog(@NonNull Context context) { 18 | super(context); 19 | setCancelable(false); 20 | } 21 | 22 | public void dismiss(){ 23 | alertDialog.dismiss(); 24 | } 25 | 26 | public void setMax(int max){ 27 | binding.progress.setMax(max); 28 | } 29 | 30 | public void updateProgress(int progress) { 31 | binding.progress.setProgressCompat(progress, true); 32 | binding.progress.setIndeterminate(false); 33 | } 34 | 35 | @Override 36 | public View onCreateView(LayoutInflater inflater) { 37 | binding = LoadingLayoutBinding.inflate(inflater); 38 | return binding.getRoot(); 39 | } 40 | 41 | @NonNull 42 | @Override 43 | public AlertDialog create() { 44 | return alertDialog = super.create(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/gson/ElementDetailTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.gson; 2 | 3 | import com.google.gson.JsonDeserializationContext; 4 | import com.google.gson.JsonDeserializer; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonParseException; 7 | import com.google.gson.JsonSerializationContext; 8 | import com.google.gson.JsonSerializer; 9 | 10 | import java.lang.reflect.Type; 11 | 12 | import de.davis.passwordmanager.database.ElementType; 13 | import de.davis.passwordmanager.database.entities.details.ElementDetail; 14 | import de.davis.passwordmanager.database.entities.details.password.Strength; 15 | 16 | public class ElementDetailTypeAdapter implements JsonSerializer, JsonDeserializer { 17 | 18 | @Override 19 | public ElementDetail deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 20 | int type = json.getAsJsonObject().get("type").getAsInt(); 21 | 22 | JsonElement strengthElement = json.getAsJsonObject().get("strength"); 23 | if(strengthElement != null && strengthElement.isJsonObject()){ 24 | json.getAsJsonObject().addProperty("strength", Strength.getEntries().get(strengthElement.getAsJsonObject().get("type").getAsInt()).name()); 25 | } 26 | 27 | return context.deserialize(json, ElementType.getTypeByTypeId(type).getElementDetailClass()); 28 | } 29 | 30 | @Override 31 | public JsonElement serialize(ElementDetail src, Type typeOfSrc, JsonSerializationContext context) { 32 | JsonElement jsonObject = context.serialize(src); 33 | jsonObject.getAsJsonObject().addProperty("type", src.getElementType().getTypeId()); 34 | return jsonObject; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/gson/annotations/ExcludeFromGson.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.gson.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface ExcludeFromGson { 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/gson/strategies/ExcludeAnnotationStrategy.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.gson.strategies; 2 | 3 | import com.google.gson.ExclusionStrategy; 4 | import com.google.gson.FieldAttributes; 5 | 6 | import de.davis.passwordmanager.gson.annotations.ExcludeFromGson; 7 | 8 | public class ExcludeAnnotationStrategy implements ExclusionStrategy { 9 | @Override 10 | public boolean shouldSkipField(FieldAttributes f) { 11 | return f.getAnnotation(ExcludeFromGson.class) != null; 12 | } 13 | 14 | @Override 15 | public boolean shouldSkipClass(Class clazz) { 16 | return false; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ktx/LifecycleOwner.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ktx 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleOwner 5 | import androidx.lifecycle.lifecycleScope 6 | import androidx.lifecycle.repeatOnLifecycle 7 | import kotlinx.coroutines.flow.Flow 8 | import kotlinx.coroutines.launch 9 | 10 | fun LifecycleOwner.doFlowInLifecycle( 11 | flow: Flow, 12 | state: Lifecycle.State = Lifecycle.State.STARTED, 13 | operationBlock: suspend Flow.() -> R 14 | ) { 15 | lifecycleScope.launch { 16 | repeatOnLifecycle(state) { 17 | operationBlock(flow) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ktx/Parcelable.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ktx 2 | 3 | import android.os.Bundle 4 | import androidx.core.os.BundleCompat 5 | 6 | fun Bundle.getParcelableCompat(key: String, clazz: Class): A? { 7 | classLoader = clazz.classLoader 8 | 9 | // Due to issues with the new getParcelable API in Android API 33, BundleCompat.getParcelable 10 | // is utilized here to ensure stable behavior across different Android versions. This workaround 11 | // selectively applies the new getParcelable implementation for API levels 34 and above, 12 | // addressing the reported bug. 13 | // For more details, refer to the Google Issue Tracker: https://issuetracker.google.com/issues/274185314 14 | // and the related discussion on Stack Overflow: https://stackoverflow.com/questions/76067109/getparcelable-crashes-due-to-null-iftable-in-agp-8 15 | return BundleCompat.getParcelable(this, key, clazz) 16 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ktx/String.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ktx 2 | 3 | fun String.capitalize(): String { 4 | return replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } 5 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/listeners/OnCreditCardEndIconClickListener.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.listeners; 2 | 3 | import android.text.method.PasswordTransformationMethod; 4 | import android.view.View; 5 | import android.widget.EditText; 6 | 7 | import com.google.android.material.textfield.TextInputLayout; 8 | 9 | import de.davis.passwordmanager.text.method.CreditCardNumberTransformationMethod; 10 | import de.davis.passwordmanager.ui.views.CheckableImageButton; 11 | 12 | public class OnCreditCardEndIconClickListener implements View.OnClickListener { 13 | 14 | private final TextInputLayout textInputLayout; 15 | 16 | public OnCreditCardEndIconClickListener(TextInputLayout textInputLayout) { 17 | this.textInputLayout = textInputLayout; 18 | } 19 | 20 | @Override 21 | public void onClick(View v) { 22 | if(!(v instanceof com.google.android.material.internal.CheckableImageButton) 23 | && !(v instanceof CheckableImageButton)) 24 | return; 25 | 26 | EditText editText = textInputLayout.getEditText(); 27 | if (editText == null) { 28 | return; 29 | } 30 | // Store the current cursor position 31 | final int selection = editText.getSelectionEnd(); 32 | if (editText.getTransformationMethod() instanceof PasswordTransformationMethod) { 33 | editText.setTransformationMethod(null); 34 | } else { 35 | editText.setTransformationMethod(CreditCardNumberTransformationMethod.getInstance()); 36 | } 37 | // And restore the cursor position 38 | if (selection >= 0) { 39 | editText.setSelection(selection); 40 | } 41 | textInputLayout.refreshEndIconDrawableState(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/listeners/OnInformationChangedListener.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.listeners; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import de.davis.passwordmanager.database.SecureElementManager; 7 | import de.davis.passwordmanager.database.dtos.SecureElement; 8 | import de.davis.passwordmanager.database.entities.details.ElementDetail; 9 | import de.davis.passwordmanager.ui.views.InformationView; 10 | 11 | public class OnInformationChangedListener implements InformationView.OnInformationChangedListener { 12 | 13 | private final E element; 14 | private final ApplyChangeToElementHelper helper; 15 | 16 | public OnInformationChangedListener(@NonNull E element, @NonNull ApplyChangeToElementHelper helper) { 17 | this.element = element; 18 | this.helper = helper; 19 | } 20 | 21 | @Override 22 | public void onInformationChanged(String information) { 23 | ElementDetail detail = helper.applyChanges(element, information); 24 | if(detail != null) 25 | element.setDetail(detail); 26 | 27 | SecureElementManager.updateElement(element); 28 | } 29 | 30 | public interface ApplyChangeToElementHelper { 31 | @Nullable 32 | ElementDetail applyChanges(E element, String changes); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/listeners/text/ExpiryDateTextWatcher.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.listeners.text; 2 | 3 | import android.text.Editable; 4 | import android.text.TextWatcher; 5 | 6 | public class ExpiryDateTextWatcher implements TextWatcher { 7 | 8 | private boolean isDeleting; 9 | private boolean isChanging; 10 | 11 | @Override 12 | public void beforeTextChanged(CharSequence charSequence, int start, int before, int after) { 13 | isDeleting = before > after; 14 | } 15 | 16 | @Override 17 | public void onTextChanged(CharSequence charSequence, int start, int before, int after) { } 18 | 19 | @Override 20 | public void afterTextChanged(Editable editable){ 21 | if(isChanging || editable.length() == 0) 22 | return; 23 | 24 | isChanging = true; 25 | if(isDeleting){ 26 | if(editable.length() == 2) 27 | editable.delete(editable.length() - 1, editable.length()); 28 | } 29 | 30 | 31 | if(editable.length() >= 2){ 32 | try{ 33 | int month = Integer.parseInt(editable.toString().substring(0, 2)); 34 | if(month > 12){ 35 | editable.delete(1, 2); 36 | } 37 | }catch (NumberFormatException ignored){} 38 | } 39 | 40 | editable.replace(0, editable.length(), format(editable.toString())); 41 | 42 | isChanging = false; 43 | } 44 | 45 | private String format(String s){ 46 | return s.replace("/", "") 47 | .replaceAll("^([2-9])$", "0$0/") 48 | .replaceAll("^(([2-9])|([2-9]/\\d+))$", "0$0") 49 | .replaceAll("^[0-1][1-9]$", "$0/") 50 | .replaceAll("^([0-1][1-9])(\\d+)$", "$1/$2") 51 | .replaceAll("^([2-9]([1-9]))/(\\d+)$", "0$2/$3"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/manager/CopyManager.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.manager; 2 | 3 | import android.content.ClipData; 4 | import android.content.ClipboardManager; 5 | import android.content.Context; 6 | import android.view.View; 7 | import android.widget.Toast; 8 | 9 | import de.davis.passwordmanager.R; 10 | import de.davis.passwordmanager.ui.views.copy.CopyView; 11 | 12 | public class CopyManager { 13 | 14 | public static void toClipboard(ClipData data, Context context){ 15 | ((ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(data); 16 | } 17 | 18 | public static class Listener implements View.OnClickListener{ 19 | @Override 20 | public void onClick(View v) { 21 | if(!(v instanceof CopyView)) 22 | return; 23 | 24 | String text = ((CopyView) v).getCopyString(); 25 | if(text.trim().isEmpty()) 26 | return; 27 | 28 | 29 | toClipboard(ClipData.newPlainText(text, text), v.getContext()); 30 | Toast.makeText(v.getContext(), R.string.copied_data, Toast.LENGTH_LONG).show(); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/nfc/Provider.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.nfc; 2 | 3 | import android.nfc.tech.IsoDep; 4 | 5 | import com.github.devnied.emvnfccard.exception.CommunicationException; 6 | import com.github.devnied.emvnfccard.parser.IProvider; 7 | 8 | import java.io.IOException; 9 | 10 | public class Provider implements IProvider { 11 | 12 | private final IsoDep isoDep; 13 | 14 | public Provider(IsoDep isoDep) { 15 | this.isoDep = isoDep; 16 | } 17 | 18 | @Override 19 | public byte[] transceive(byte[] pCommand) throws CommunicationException { 20 | byte[] response; 21 | try { 22 | response = isoDep.transceive(pCommand); 23 | } catch (IOException e) { 24 | throw new CommunicationException(e.getMessage()); 25 | } 26 | 27 | return response; 28 | } 29 | 30 | @Override 31 | public byte[] getAt() { 32 | return isoDep.getHistoricalBytes(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/security/BiometricAuthentication.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.security 2 | 3 | import android.content.Context 4 | import androidx.biometric.BiometricManager 5 | import androidx.biometric.BiometricPrompt 6 | import androidx.biometric.BiometricPrompt.PromptInfo 7 | import androidx.core.content.ContextCompat 8 | import androidx.fragment.app.Fragment 9 | import de.davis.passwordmanager.R 10 | import de.davis.passwordmanager.utils.PreferenceUtil 11 | 12 | object BiometricAuthentication { 13 | 14 | @JvmStatic 15 | fun auth( 16 | fragment: Fragment, 17 | callback: BiometricPrompt.AuthenticationCallback, 18 | negativeButtonText: String = fragment.requireContext().getString(R.string.use_password) 19 | ) { 20 | if (!isActivated(fragment.requireContext())) 21 | return 22 | 23 | val promptInfo = PromptInfo.Builder().apply { 24 | setTitle(fragment.getString(R.string.authentication_title)) 25 | setDescription(fragment.getString(R.string.authentication_description)) 26 | setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG) 27 | setNegativeButtonText(negativeButtonText) 28 | }.build() 29 | 30 | val biometricPrompt = BiometricPrompt( 31 | fragment, 32 | ContextCompat.getMainExecutor(fragment.requireContext()), 33 | callback 34 | ) 35 | biometricPrompt.authenticate(promptInfo) 36 | } 37 | 38 | @JvmStatic 39 | fun isAvailable(context: Context): Boolean { 40 | val manager = BiometricManager.from(context) 41 | return manager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS 42 | } 43 | 44 | @JvmStatic 45 | fun isActivated(context: Context): Boolean { 46 | return isAvailable(context) && PreferenceUtil.getBoolean( 47 | context, 48 | R.string.preference_biometrics, 49 | false 50 | ) 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/security/mainpassword/MainPassword.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.security.mainpassword 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | import java.time.Instant 6 | 7 | fun UserMainPassword.toNormal() = MainPassword( 8 | hexHash, Instant.ofEpochSecond( 9 | createdAt.seconds, 10 | createdAt.nanos.toLong() 11 | ) 12 | ) 13 | 14 | @Parcelize 15 | data class MainPassword(val hexHash: String, val createdAt: Instant) : Parcelable 16 | 17 | val MainPassword.isEmpty: Boolean 18 | get() = hexHash.isBlank() -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/security/mainpassword/MainPasswordManager.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.security.mainpassword 2 | 3 | import android.content.Context 4 | import androidx.datastore.dataStore 5 | import de.davis.passwordmanager.PasswordManagerApplication 6 | import kotlinx.coroutines.flow.first 7 | 8 | object MainPasswordManager { 9 | 10 | private val Context.mainPasswordDataStore by dataStore( 11 | "main-password.db", 12 | MainPasswordSerializer 13 | ) 14 | 15 | suspend fun getPassword(): MainPassword = 16 | PasswordManagerApplication.getAppContext().mainPasswordDataStore.data.first().toNormal() 17 | 18 | @OptIn(ExperimentalStdlibApi::class) 19 | suspend fun updatePassword(hashedPassword: ByteArray) { 20 | PasswordManagerApplication.getAppContext().mainPasswordDataStore.updateData { 21 | it.copy { 22 | hexHash = hashedPassword.toHexString() 23 | } 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/security/mainpassword/MainPasswordSerializer.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.security.mainpassword 2 | 3 | import androidx.datastore.core.CorruptionException 4 | import androidx.datastore.core.Serializer 5 | import com.google.protobuf.InvalidProtocolBufferException 6 | import java.io.InputStream 7 | import java.io.OutputStream 8 | 9 | object MainPasswordSerializer : Serializer { 10 | 11 | override val defaultValue: UserMainPassword = UserMainPassword.getDefaultInstance() 12 | 13 | override suspend fun readFrom(input: InputStream): UserMainPassword { 14 | try { 15 | return UserMainPassword.parseFrom(input) 16 | } catch (exception: InvalidProtocolBufferException) { 17 | throw CorruptionException("Cannot read proto.", exception) 18 | } 19 | } 20 | 21 | override suspend fun writeTo(t: UserMainPassword, output: OutputStream) = t.writeTo(output) 22 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/AutoSaveHandler.kt: -------------------------------------------------------------------------------- 1 | @file:RequiresApi(Build.VERSION_CODES.O) 2 | 3 | package de.davis.passwordmanager.services.autofill 4 | 5 | import android.content.pm.PackageManager 6 | import android.os.Build 7 | import android.service.autofill.SaveRequest 8 | import androidx.annotation.RequiresApi 9 | import de.davis.passwordmanager.database.dtos.SecureElement 10 | import de.davis.passwordmanager.database.entities.details.password.PasswordDetails 11 | import de.davis.passwordmanager.services.autofill.entities.SaveField 12 | import de.davis.passwordmanager.services.autofill.entities.SaveForm 13 | import de.davis.passwordmanager.services.autofill.extensions.findChildById 14 | import de.davis.passwordmanager.services.autofill.extensions.getWindowNodes 15 | import java.net.URI 16 | 17 | fun SaveForm.getElement(request: SaveRequest, packageManager: PackageManager): SecureElement { 18 | val origin: String = url ?: "" 19 | val username = identifierSaveField?.findNode(request)?.autofillValue?.textValue ?: "" 20 | val password = passwordSaveField?.findNode(request)?.autofillValue?.textValue ?: "" 21 | val packageName = request.fillContexts.firstOrNull()?.structure?.activityComponent?.packageName 22 | 23 | val title = packageName.orEmpty().let { 24 | URI(origin).host ?: packageManager.run { 25 | runCatching { 26 | getApplicationLabel(getApplicationInfo(it, 0)).toString() 27 | }.getOrDefault(it) 28 | } 29 | } 30 | 31 | return SecureElement(title, PasswordDetails(password.toString(), origin, username.toString())) 32 | } 33 | 34 | private fun SaveField.findNode(request: SaveRequest) = request.fillContexts.firstOrNull { 35 | it.requestId == requestId 36 | }?.let { fillContext -> 37 | fillContext.getWindowNodes().firstNotNullOfOrNull { 38 | it.rootViewNode.findChildById(autofillId) 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/SaveActivity.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.widget.Toast 7 | import androidx.appcompat.app.AppCompatActivity 8 | import de.davis.passwordmanager.R 9 | import de.davis.passwordmanager.database.dtos.SecureElement 10 | import de.davis.passwordmanager.databinding.EmptyFragmentContainerBinding 11 | import de.davis.passwordmanager.ktx.getParcelableCompat 12 | import de.davis.passwordmanager.manager.ActivityResultManager 13 | 14 | class SaveActivity : AppCompatActivity() { 15 | 16 | override fun onCreate(savedInstanceState: Bundle?) { 17 | super.onCreate(savedInstanceState) 18 | val binding = EmptyFragmentContainerBinding.inflate(layoutInflater, null, false) 19 | setContentView(binding.root) 20 | 21 | val secureElement = intent.extras?.getSecureElement() ?: run { 22 | Toast.makeText(this, R.string.error_title, Toast.LENGTH_SHORT).show() 23 | finish() 24 | return 25 | } 26 | 27 | ActivityResultManager.getOrCreateManager(javaClass, this) 28 | .apply { registerCreate { finish() } } 29 | .launchCreate(secureElement, this) 30 | } 31 | 32 | companion object { 33 | private const val EXTRA_AUTOFILL_SAVE_ELEMENT = 34 | "de.davis.passwordmanager.extra.AUTOFILL_SAVE_ELEMENT" 35 | 36 | fun newIntent(context: Context, secureElement: SecureElement): Intent = 37 | Intent(context, SaveActivity::class.java).putExtra( 38 | EXTRA_AUTOFILL_SAVE_ELEMENT, secureElement 39 | ) 40 | } 41 | 42 | private fun Bundle.getSecureElement(): SecureElement? = getParcelableCompat( 43 | EXTRA_AUTOFILL_SAVE_ELEMENT, SecureElement::class.java 44 | ) 45 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/builder/InlinePresentationBuilder.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill.builder 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.PendingIntent 5 | import android.graphics.drawable.Icon 6 | import android.os.Build 7 | import android.service.autofill.InlinePresentation 8 | import android.widget.inline.InlinePresentationSpec 9 | import androidx.annotation.RequiresApi 10 | import androidx.autofill.inline.v1.InlineSuggestionUi 11 | 12 | @SuppressLint("RestrictedApi") 13 | @RequiresApi(Build.VERSION_CODES.R) 14 | object InlinePresentationBuilder { 15 | 16 | fun createBasicPresentation( 17 | title: String, 18 | subtitle: String? = null, 19 | icon: Icon? = null, 20 | inlinePresentationSpec: InlinePresentationSpec, 21 | pendingIntent: PendingIntent 22 | ): InlinePresentation { 23 | return InlinePresentation( 24 | InlineSuggestionUi.newContentBuilder(pendingIntent).apply { 25 | setContentDescription("$title $subtitle") 26 | setTitle(title) 27 | if (subtitle != null) 28 | setSubtitle(subtitle) 29 | 30 | if (icon != null) 31 | setStartIcon(icon) 32 | }.build().slice, 33 | inlinePresentationSpec, 34 | false 35 | ) 36 | } 37 | 38 | fun createPinnedPresentation( 39 | icon: Icon? = null, 40 | inlinePresentationSpec: InlinePresentationSpec, 41 | pendingIntent: PendingIntent 42 | ): InlinePresentation { 43 | return InlinePresentation( 44 | InlineSuggestionUi.newContentBuilder(pendingIntent).apply { 45 | setContentDescription("Pinned Autofill option") 46 | if (icon != null) 47 | setStartIcon(icon) 48 | }.build().slice, 49 | inlinePresentationSpec, 50 | true 51 | ) 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/builder/SuggestedDatasetBuilder.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill.builder 2 | 3 | import android.service.autofill.Dataset 4 | import de.davis.passwordmanager.database.SecureElementManager 5 | import de.davis.passwordmanager.database.entities.details.password.PasswordDetails 6 | import de.davis.passwordmanager.services.autofill.entities.AutofillForm 7 | import de.davis.passwordmanager.utils.BrowserUtil 8 | 9 | object SuggestedDatasetBuilder { 10 | 11 | suspend fun AutofillForm.getNSuggestions( 12 | n: Int, 13 | typeId: Int, 14 | builder: (TextProvider, requestCode: Int) -> Dataset 15 | ): List = url?.let { url -> 16 | SecureElementManager.getSecureElements(typeId) 17 | .filter { 18 | (it.detail as PasswordDetails).origin.couldBeUrl(url) || 19 | it.title.couldBeUrl(url) 20 | } 21 | .take(n) 22 | .mapIndexed { index, element -> builder(element.getTextProvider(), index) } 23 | } ?: emptyList() 24 | 25 | 26 | private fun String.couldBeUrl(actualUrl: String): Boolean = 27 | BrowserUtil.couldBeUrl(this, actualUrl) 28 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/builder/TextProvider.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill.builder 2 | 3 | import de.davis.passwordmanager.database.dtos.SecureElement 4 | import de.davis.passwordmanager.database.entities.details.password.PasswordDetails 5 | 6 | data class TextProvider( 7 | val title: String, 8 | val subtitle: String? = null, 9 | val element: SecureElement? = null 10 | ) { 11 | constructor(element: SecureElement, subtitleProvider: (SecureElement) -> String?) : this( 12 | element = element, 13 | title = element.title, 14 | subtitle = subtitleProvider(element) 15 | ) 16 | } 17 | 18 | fun SecureElement.getTextProvider() = 19 | TextProvider(this) { (this.detail as PasswordDetails).username.ifBlank { "----" } } 20 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/entities/AutofillField.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill.entities 2 | 3 | import android.os.Parcelable 4 | import android.view.autofill.AutofillId 5 | import kotlinx.parcelize.Parcelize 6 | 7 | @Parcelize 8 | data class AutofillField( 9 | val autofillId: AutofillId, 10 | val userCredentialsType: UserCredentialsType 11 | ) : Parcelable -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/entities/AutofillForm.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill.entities 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | 6 | @Parcelize 7 | data class AutofillForm( 8 | val autofillFields: MutableList = mutableListOf(), 9 | var url: String? = null 10 | ) : Parcelable -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/entities/AutofillPair.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill.entities 2 | 3 | data class AutofillPair(val autofillField: AutofillField, val data: String) -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/entities/SaveField.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill.entities 2 | 3 | import android.os.Parcelable 4 | import android.view.autofill.AutofillId 5 | import kotlinx.parcelize.Parcelize 6 | 7 | @Parcelize 8 | data class SaveField( 9 | val autofillId: AutofillId, 10 | val userCredentialsType: UserCredentialsType, 11 | val requestId: Int 12 | ) : Parcelable 13 | 14 | fun AutofillField.toSaveField(requestId: Int) = 15 | SaveField(autofillId, userCredentialsType, requestId) 16 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/entities/SaveForm.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill.entities 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.IgnoredOnParcel 5 | import kotlinx.parcelize.Parcelize 6 | 7 | @Parcelize 8 | data class SaveForm( 9 | val identifierSaveField: SaveField? = null, 10 | val passwordSaveField: SaveField? = null, 11 | val url: String? = null 12 | ) : Parcelable { 13 | 14 | @IgnoredOnParcel 15 | val userCredentialsTypes = listOfNotNull( 16 | identifierSaveField?.userCredentialsType, 17 | passwordSaveField?.userCredentialsType 18 | ) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/entities/TraverseNode.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill.entities 2 | 3 | import android.app.assist.AssistStructure.ViewNode 4 | 5 | class TraverseNode(val node: ViewNode, val parent: TraverseNode? = null) 6 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/entities/UserCredentialsType.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill.entities 2 | 3 | import android.os.Parcelable 4 | import kotlinx.parcelize.Parcelize 5 | 6 | sealed interface UserCredentialsType : Parcelable { 7 | 8 | @Parcelize 9 | data object Password : UserCredentialsType 10 | 11 | @Parcelize 12 | data object Unidentified : UserCredentialsType 13 | } 14 | 15 | sealed interface Identifier : UserCredentialsType { 16 | 17 | @Parcelize 18 | data object Username : Identifier 19 | 20 | @Parcelize 21 | data object Email : Identifier 22 | 23 | @Parcelize 24 | data object PhoneNumber : Identifier 25 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/extensions/ClientStateExt.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill.extensions 2 | 3 | import android.os.Bundle 4 | import de.davis.passwordmanager.ktx.getParcelableCompat 5 | import de.davis.passwordmanager.services.autofill.entities.Identifier 6 | import de.davis.passwordmanager.services.autofill.entities.SaveField 7 | import de.davis.passwordmanager.services.autofill.entities.SaveForm 8 | import de.davis.passwordmanager.services.autofill.entities.UserCredentialsType 9 | 10 | private const val KEY_SAVE_FORM = "key_save_form" 11 | 12 | fun Bundle.put(saveField: SaveField? = null, url: String? = null) { 13 | val saveForm = getParcelableCompat(KEY_SAVE_FORM, SaveForm::class.java) ?: SaveForm() 14 | val updatedSaveForm = when (saveField?.userCredentialsType) { 15 | is Identifier -> saveForm.copy(identifierSaveField = saveField) 16 | UserCredentialsType.Password -> saveForm.copy(passwordSaveField = saveField) 17 | else -> saveForm 18 | }.copy(url = url ?: saveForm.url) 19 | 20 | putParcelable(KEY_SAVE_FORM, updatedSaveForm) 21 | } 22 | 23 | fun Bundle.get(): SaveForm = 24 | getParcelableCompat(KEY_SAVE_FORM, SaveForm::class.java) ?: SaveForm() 25 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/extensions/FillContextExt.kt: -------------------------------------------------------------------------------- 1 | @file:RequiresApi(Build.VERSION_CODES.O) 2 | 3 | package de.davis.passwordmanager.services.autofill.extensions 4 | 5 | import android.app.assist.AssistStructure 6 | import android.os.Build 7 | import android.service.autofill.FillContext 8 | import androidx.annotation.RequiresApi 9 | 10 | fun Collection.getWindowNodes(): List { 11 | val fillContext = lastOrNull() ?: return emptyList() 12 | return (0 until fillContext.structure.windowNodeCount).map { 13 | fillContext.structure.getWindowNodeAt(it) 14 | } 15 | } 16 | 17 | fun FillContext.getWindowNodes(): List = listOf(this).getWindowNodes() -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/extensions/NodeExt.kt: -------------------------------------------------------------------------------- 1 | @file:RequiresApi(Build.VERSION_CODES.O) 2 | 3 | package de.davis.passwordmanager.services.autofill.extensions 4 | 5 | import android.app.assist.AssistStructure.ViewNode 6 | import android.app.assist.AssistStructure.WindowNode 7 | import android.os.Build 8 | import android.view.autofill.AutofillId 9 | import androidx.annotation.RequiresApi 10 | 11 | fun WindowNode.getPackageName() = title.split("/").first() 12 | 13 | fun ViewNode.findChildById(id: AutofillId): ViewNode? { 14 | if (autofillId == id) return this 15 | for (i in 0 until childCount) { 16 | val child = getChildAt(i).findChildById(id) 17 | if (child != null) return child 18 | } 19 | return null 20 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/services/autofill/extensions/PendingIntentExt.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.services.autofill.extensions 2 | 3 | import android.app.PendingIntent 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Build 7 | import androidx.annotation.RequiresApi 8 | import de.davis.passwordmanager.database.dtos.SecureElement 9 | import de.davis.passwordmanager.services.autofill.SaveActivity 10 | import de.davis.passwordmanager.services.autofill.SelectionActivity 11 | import de.davis.passwordmanager.services.autofill.entities.AutofillForm 12 | 13 | 14 | private const val AUTOFILL_PENDING_INTENT_FLAGS = 15 | PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_UPDATE_CURRENT 16 | 17 | @RequiresApi(Build.VERSION_CODES.O) 18 | fun Context.getSelectionPendingIntent( 19 | requestCode: Int, 20 | autofillForm: AutofillForm, 21 | secureElement: SecureElement? = null 22 | ): PendingIntent = 23 | PendingIntent.getActivity( 24 | this, 25 | requestCode, 26 | SelectionActivity.newIntent(this, autofillForm, secureElement), 27 | AUTOFILL_PENDING_INTENT_FLAGS 28 | ) 29 | 30 | fun Context.getSavePendingIntent( 31 | requestCode: Int, 32 | element: SecureElement 33 | ): PendingIntent = 34 | PendingIntent.getActivity( 35 | this, 36 | requestCode, 37 | SaveActivity.newIntent(this, element), 38 | AUTOFILL_PENDING_INTENT_FLAGS 39 | ) 40 | 41 | internal fun Context.getOnLongClickPendingIntent() = PendingIntent.getService( 42 | this, 43 | 0, 44 | Intent(), 45 | PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE 46 | ) -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/text/method/CreditCardNumberTransformationMethod.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.text.method; 2 | 3 | import android.text.method.PasswordTransformationMethod; 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | public class CreditCardNumberTransformationMethod extends PasswordTransformationMethod { 9 | 10 | private static final char DOT = '•'; 11 | 12 | @Override 13 | public CharSequence getTransformation(CharSequence source, View view) { 14 | return new CreditCardCharSequence(source); 15 | } 16 | 17 | private record CreditCardCharSequence(CharSequence source) implements CharSequence { 18 | 19 | @Override 20 | public int length() { 21 | return source.length(); 22 | } 23 | 24 | @Override 25 | public char charAt(int index) { 26 | char c = source.charAt(index); 27 | if (!Character.isWhitespace(c)) { 28 | return DOT; 29 | } 30 | return c; 31 | } 32 | 33 | @NonNull 34 | @Override 35 | public CharSequence subSequence(int start, int end) { 36 | return source.subSequence(start, end); 37 | } 38 | } 39 | 40 | public static PasswordTransformationMethod getInstance() { 41 | if (sInstance != null) 42 | return sInstance; 43 | 44 | sInstance = new CreditCardNumberTransformationMethod(); 45 | return sInstance; 46 | } 47 | private static PasswordTransformationMethod sInstance; 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/BaseMainActivity.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | 6 | import androidx.appcompat.app.AppCompatActivity; 7 | import androidx.navigation.NavController; 8 | import androidx.navigation.fragment.NavHostFragment; 9 | import androidx.navigation.ui.NavigationUI; 10 | 11 | import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton; 12 | import com.google.android.material.navigation.NavigationBarView; 13 | 14 | import de.davis.passwordmanager.R; 15 | import de.davis.passwordmanager.ui.views.AddBottomSheet; 16 | 17 | public class BaseMainActivity extends AppCompatActivity { 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | 24 | NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment); 25 | if(navHostFragment == null) 26 | return; 27 | 28 | NavigationBarView navigationBarView = findViewById(R.id.navigationView); 29 | NavController navController = navHostFragment.getNavController(); 30 | NavigationUI.setupWithNavController(navigationBarView, navController); 31 | 32 | 33 | getFAB().setOnClickListener(v -> new AddBottomSheet().show(getSupportFragmentManager(), "add-bottom-sheet")); 34 | 35 | float screenWidthDp = getResources().getConfiguration().screenWidthDp; 36 | if(screenWidthDp >= 600) 37 | return; 38 | 39 | var bottomSectionHandler = new BottomSectionHandler(navigationBarView, (ExtendedFloatingActionButton) getFAB(), this); 40 | bottomSectionHandler.handle(); 41 | } 42 | 43 | private View getFAB(){ 44 | float screenWidthDp = getResources().getConfiguration().screenWidthDp; 45 | return screenWidthDp >= 600 ? findViewById(R.id.add_rail) : findViewById(R.id.add); 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/GridLayoutManager.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import androidx.recyclerview.widget.GridLayoutManager 6 | 7 | class GridLayoutManager( 8 | val context: Context, 9 | attributeSet: AttributeSet? = null, 10 | defStyleAttr: Int = 0, 11 | defStyleRes: Int = 0 12 | ) : GridLayoutManager(context, attributeSet, defStyleAttr, defStyleRes) { 13 | 14 | init { 15 | spanCount = 2 16 | } 17 | 18 | override fun getPaddingBottom(): Int { 19 | return getPaddingBottom(context) 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/LayoutManager.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui 2 | 3 | import android.content.Context 4 | import android.util.TypedValue 5 | 6 | internal fun getPaddingBottom(context: Context): Int { 7 | val dip = (56 + 16 * 2).toFloat() 8 | 9 | return TypedValue.applyDimension( 10 | TypedValue.COMPLEX_UNIT_DIP, 11 | dip, 12 | context.resources.displayMetrics 13 | ).toInt() 14 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/LinearLayoutManager.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | public class LinearLayoutManager extends androidx.recyclerview.widget.LinearLayoutManager { 7 | 8 | private final Context context; 9 | 10 | public LinearLayoutManager(Context context) { 11 | super(context); 12 | this.context = context; 13 | } 14 | 15 | public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) { 16 | super(context, orientation, reverseLayout); 17 | this.context = context; 18 | } 19 | 20 | public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 21 | super(context, attrs, defStyleAttr, defStyleRes); 22 | this.context = context; 23 | } 24 | 25 | @Override 26 | public boolean isAutoMeasureEnabled() { 27 | return false; 28 | } 29 | 30 | @Override 31 | public int getPaddingBottom() { 32 | return LayoutManagerKt.getPaddingBottom(context); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/auth/AuthenticationResult.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.auth 2 | 3 | sealed class AuthenticationResult { 4 | 5 | data object Success : AuthenticationResult() 6 | data class Error(val authenticationState: AuthenticationState) : AuthenticationResult() 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/auth/AuthenticationState.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.auth 2 | 3 | import androidx.annotation.StringRes 4 | 5 | data class AuthenticationState( 6 | @StringRes val passwordError: Int? = null, 7 | @StringRes val newPasswordError: Int? = null, 8 | @StringRes val confirmPasswordError: Int? = null 9 | ) 10 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/auth/BasicAuthenticationFragment.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.auth 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import androidx.fragment.app.Fragment 7 | import androidx.fragment.app.viewModels 8 | import de.davis.passwordmanager.ktx.getParcelableCompat 9 | import de.davis.passwordmanager.security.mainpassword.MainPassword 10 | import de.davis.passwordmanager.ui.MainActivity 11 | 12 | open class BasicAuthenticationFragment : Fragment() { 13 | 14 | protected val viewModel: AuthenticationViewModel by viewModels() 15 | 16 | protected lateinit var authenticationRequest: AuthenticationRequest 17 | protected lateinit var mainPassword: MainPassword 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | 21 | arguments?.let { 22 | authenticationRequest = it.getParcelableCompat( 23 | AuthenticationActivity.KEY_AUTHENTICATION_REQUEST, 24 | AuthenticationRequest::class.java 25 | )!! 26 | mainPassword = it.getParcelableCompat( 27 | AuthenticationActivity.KEY_MAIN_PASSWORD, 28 | MainPassword::class.java 29 | )!! 30 | } 31 | } 32 | 33 | protected fun success() { 34 | val intent = Intent() 35 | 36 | authenticationRequest.additionalExtras?.let { 37 | intent.putExtras(it) 38 | } 39 | 40 | requireActivity().setResult( 41 | Activity.RESULT_OK, 42 | intent 43 | ) 44 | 45 | if (requireActivity().intent.action == Intent.ACTION_MAIN) 46 | startActivity(Intent(requireContext(), MainActivity::class.java)) 47 | else 48 | authenticationRequest.intent?.let { startActivity(it) } 49 | 50 | requireActivity().finish() 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/callbacks/SearchViewBackPressedHandler.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.callbacks; 2 | 3 | import androidx.activity.OnBackPressedCallback; 4 | import androidx.annotation.NonNull; 5 | 6 | import com.google.android.material.search.SearchView; 7 | 8 | public class SearchViewBackPressedHandler extends OnBackPressedCallback implements SearchView.TransitionListener { 9 | 10 | private final SearchView searchView; 11 | 12 | public SearchViewBackPressedHandler(SearchView searchView) { 13 | super(searchView.isEnabled() && searchView.isShowing()); 14 | this.searchView = searchView; 15 | searchView.addTransitionListener(this); 16 | } 17 | 18 | @Override 19 | public void handleOnBackPressed() { 20 | searchView.hide(); 21 | } 22 | 23 | @Override 24 | public void onStateChanged(@NonNull SearchView searchView, @NonNull SearchView.TransitionState previousState, @NonNull SearchView.TransitionState newState) { 25 | setEnabled(searchView.isEnabled() && searchView.isShowing()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/callbacks/SlidingBackPaneManager.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.callbacks; 2 | 3 | import android.view.View; 4 | 5 | import androidx.activity.OnBackPressedCallback; 6 | import androidx.annotation.NonNull; 7 | import androidx.slidingpanelayout.widget.SlidingPaneLayout; 8 | 9 | import de.davis.passwordmanager.ui.viewmodels.ScrollingViewModel; 10 | 11 | public class SlidingBackPaneManager extends OnBackPressedCallback implements SlidingPaneLayout.PanelSlideListener { 12 | 13 | private final SlidingPaneLayout slidingPaneLayout; 14 | private final ScrollingViewModel scrollingViewModel; 15 | 16 | public SlidingBackPaneManager(SlidingPaneLayout slidingPaneLayout, ScrollingViewModel scrollingViewModel) { 17 | super(slidingPaneLayout.isEnabled() && slidingPaneLayout.isOpen()); 18 | this.slidingPaneLayout = slidingPaneLayout; 19 | this.scrollingViewModel = scrollingViewModel; 20 | 21 | slidingPaneLayout.addPanelSlideListener(this); 22 | slidingPaneLayout.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> updateState()); 23 | } 24 | 25 | 26 | private void updateState(){ 27 | setEnabled(slidingPaneLayout.isEnabled() && slidingPaneLayout.isOpen()); 28 | } 29 | 30 | @Override 31 | public void handleOnBackPressed() { 32 | slidingPaneLayout.closePane(); 33 | scrollingViewModel.setFabVisibility(true); 34 | } 35 | 36 | @Override 37 | public void onPanelSlide(@NonNull View panel, float slideOffset) {} 38 | 39 | @Override 40 | public void onPanelOpened(@NonNull View panel) { 41 | updateState(); 42 | } 43 | 44 | @Override 45 | public void onPanelClosed(@NonNull View panel) { 46 | updateState(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/dashboard/SecureElementDiffCallback.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.dashboard 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import de.davis.passwordmanager.database.dtos.Item 5 | 6 | class SecureElementDiffCallback( 7 | private val oldItems: List, 8 | private val newItems: List 9 | ) : DiffUtil.Callback() { 10 | override fun getOldListSize(): Int { 11 | return oldItems.size 12 | } 13 | 14 | override fun getNewListSize(): Int { 15 | return newItems.size 16 | } 17 | 18 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 19 | val oldItem = oldItems[oldItemPosition] 20 | val newItem = newItems[newItemPosition] 21 | return oldItem::class.isInstance(newItem) && oldItems[oldItemPosition].id == newItems[newItemPosition].id 22 | } 23 | 24 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 25 | val oldItem = oldItems[oldItemPosition] 26 | val newItem = newItems[newItemPosition] 27 | return oldItem == newItem 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/dashboard/menuprovider/DefaultElementMenuProvider.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.dashboard.menuprovider 2 | 3 | import android.view.Menu 4 | import android.view.MenuInflater 5 | import android.view.MenuItem 6 | import androidx.core.view.MenuHost 7 | import androidx.core.view.MenuProvider 8 | import androidx.fragment.app.FragmentManager 9 | import de.davis.passwordmanager.R 10 | import de.davis.passwordmanager.database.dtos.Item 11 | import de.davis.passwordmanager.ui.dashboard.DashboardAdapter 12 | import de.davis.passwordmanager.ui.views.FilterBottomSheet 13 | import de.davis.passwordmanager.ui.views.OptionBottomSheet 14 | 15 | class DefaultElementMenuProvider( 16 | val manager: FragmentManager, 17 | private val adapter: DashboardAdapter 18 | ) : MenuProvider { 19 | 20 | var filterVisible: Boolean = false 21 | var selectedElements: List = emptyList() 22 | 23 | override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { 24 | menuInflater.inflate(R.menu.view_menu, menu) 25 | } 26 | 27 | override fun onPrepareMenu(menu: Menu) { 28 | super.onPrepareMenu(menu) 29 | menu.apply { 30 | findItem(R.id.more).setVisible(selectedElements.isNotEmpty()) 31 | findItem(R.id.filter).setVisible(filterVisible) 32 | } 33 | } 34 | 35 | override fun onMenuItemSelected(menuItem: MenuItem): Boolean { 36 | if (menuItem.itemId == R.id.more) { 37 | val optionBottomSheet = OptionBottomSheet(selectedElements) 38 | optionBottomSheet.show(manager, "MoreDialog") 39 | adapter.clearSelection() 40 | } else if (menuItem.itemId == R.id.filter) { 41 | FilterBottomSheet().show(manager, "FilterDialog") 42 | } 43 | return true 44 | } 45 | 46 | fun updateMenu(menuHost: MenuHost, block: DefaultElementMenuProvider.() -> Unit) { 47 | apply(block) 48 | menuHost.invalidateMenu() 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/dashboard/selection/KeyProvider.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.dashboard.selection; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | import androidx.recyclerview.selection.ItemKeyProvider; 6 | import androidx.recyclerview.widget.RecyclerView; 7 | 8 | import java.util.Objects; 9 | 10 | public class KeyProvider extends ItemKeyProvider { 11 | 12 | private final RecyclerView recyclerView; 13 | 14 | public KeyProvider(RecyclerView recyclerView) { 15 | super(SCOPE_MAPPED); 16 | this.recyclerView = recyclerView; 17 | } 18 | 19 | @Nullable 20 | @Override 21 | public Long getKey(int position) { 22 | return Objects.requireNonNull(recyclerView.getAdapter()).getItemId(position); 23 | } 24 | 25 | @Override 26 | public int getPosition(@NonNull Long key) { 27 | RecyclerView.ViewHolder holder = recyclerView.findViewHolderForItemId(key); 28 | return holder != null ? holder.getAdapterPosition() : RecyclerView.NO_POSITION; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/dashboard/selection/SecureElementDetailsLookup.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.dashboard.selection; 2 | 3 | import android.view.MotionEvent; 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | import androidx.recyclerview.selection.ItemDetailsLookup; 9 | import androidx.recyclerview.widget.RecyclerView; 10 | 11 | public class SecureElementDetailsLookup extends ItemDetailsLookup { 12 | 13 | private final RecyclerView recyclerView; 14 | 15 | public SecureElementDetailsLookup(RecyclerView recyclerView) { 16 | this.recyclerView = recyclerView; 17 | } 18 | 19 | @Nullable 20 | @Override 21 | public ItemDetails getItemDetails(@NonNull MotionEvent e) { 22 | View view = recyclerView.findChildViewUnder(e.getX(), e.getY()); 23 | if(view == null) 24 | return null; 25 | 26 | RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(view); 27 | if(!(holder instanceof ItemDetailsLookup)) 28 | return null; 29 | 30 | return ((ItemDetailsLookup) holder).getItemDetails(); 31 | } 32 | 33 | public interface ItemDetailsLookup { 34 | ItemDetails getItemDetails(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/dashboard/viewholders/BasicViewHolder.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.dashboard.viewholders; 2 | 3 | import android.view.View; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.recyclerview.selection.ItemDetailsLookup; 7 | import androidx.recyclerview.widget.RecyclerView; 8 | 9 | import de.davis.passwordmanager.database.dtos.Item; 10 | import de.davis.passwordmanager.ui.dashboard.selection.SecureElementDetailsLookup; 11 | 12 | public abstract class BasicViewHolder extends RecyclerView.ViewHolder implements SecureElementDetailsLookup.ItemDetailsLookup { 13 | 14 | public BasicViewHolder(@NonNull View itemView) { 15 | super(itemView); 16 | } 17 | 18 | public void bind(@NonNull T item, String filter, OnItemClickedListener onItemClickedListener, boolean selected){ 19 | bindGeneral(item, filter, onItemClickedListener); 20 | handleSelectionState(selected); 21 | } 22 | 23 | protected abstract void bindGeneral(@NonNull T item, String filter, OnItemClickedListener onItemClickedListener); 24 | 25 | protected abstract void handleSelectionState(boolean selected); 26 | 27 | @Override 28 | public ItemDetailsLookup.ItemDetails getItemDetails() { 29 | return new ItemDetailsLookup.ItemDetails<>() { 30 | @Override 31 | public int getPosition() { 32 | return getAbsoluteAdapterPosition(); 33 | } 34 | 35 | @Override 36 | public Long getSelectionKey() { 37 | return getItemId(); 38 | } 39 | }; 40 | } 41 | 42 | public interface OnItemClickedListener { 43 | void onClicked(T element); 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/elements/NoElementFragment.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.elements; 2 | 3 | import androidx.fragment.app.Fragment; 4 | 5 | import de.davis.passwordmanager.R; 6 | 7 | public class NoElementFragment extends Fragment { 8 | 9 | public NoElementFragment() { 10 | super(R.layout.fragment_no_item_selected); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/elements/SEBaseUi.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.elements; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | 11 | import de.davis.passwordmanager.database.dtos.SecureElement; 12 | 13 | public interface SEBaseUi { 14 | 15 | class Initiator{ 16 | public static SecureElement initiate(SEBaseUi seBaseUi, Bundle bundle, String key){ 17 | if(bundle == null || !bundle.containsKey(key)) 18 | return null; 19 | 20 | Object object = bundle.getParcelable(key); 21 | SecureElement element = null; 22 | try { 23 | element = (SecureElement) object; 24 | }catch (ClassCastException ignored){} 25 | 26 | if(element != null) 27 | seBaseUi.fillInElement(element); 28 | 29 | return element; 30 | } 31 | } 32 | 33 | SecureElement getElement(); 34 | 35 | void fillInElement(@NonNull SecureElement secureElement); 36 | View getContentView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState); 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/elements/SEViewActivity.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.elements; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.Nullable; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | 8 | import de.davis.passwordmanager.Keys; 9 | import de.davis.passwordmanager.database.dtos.SecureElement; 10 | 11 | public abstract class SEViewActivity extends AppCompatActivity implements SEBaseUi { 12 | 13 | private SecureElement element; 14 | 15 | @Override 16 | protected void onCreate(@Nullable Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | setContentView(getContentView(getLayoutInflater(), null, null)); 19 | element = Initiator.initiate(this, getIntent().getExtras(), Keys.KEY_OLD); 20 | } 21 | 22 | @Override 23 | public SecureElement getElement() { 24 | return element; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/elements/SEViewFragment.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.elements; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | import androidx.fragment.app.Fragment; 11 | 12 | import de.davis.passwordmanager.database.dtos.SecureElement; 13 | 14 | public abstract class SEViewFragment extends Fragment implements SEBaseUi { 15 | 16 | private SecureElement element; 17 | 18 | @Nullable 19 | @Override 20 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 21 | View view = getContentView(inflater, container, savedInstanceState); 22 | if(view != null) 23 | return view; 24 | 25 | return super.onCreateView(inflater, container, savedInstanceState); 26 | } 27 | 28 | @Override 29 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 30 | super.onViewCreated(view, savedInstanceState); 31 | element = Initiator.initiate(this, getArguments(), "element"); 32 | } 33 | 34 | @Override 35 | public SecureElement getElement() { 36 | return element; 37 | } 38 | 39 | protected void setElement(SecureElement element) { 40 | this.element = element; 41 | fillInElement(element); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/settings/BaseVersionFragment.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.settings; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | import androidx.fragment.app.Fragment; 11 | 12 | import de.davis.passwordmanager.databinding.FragmentUpdaterBinding; 13 | import de.davis.passwordmanager.utils.VersionUtil; 14 | import de.davis.passwordmanager.version.CurrentVersion; 15 | 16 | /** 17 | * A basic screen that displays information about the software version. 18 | * This class is intended to be subclassed by any product flavor with the dimension "market" 19 | * in a class called VersionFragment under the package de.davis.passwordmanager.ui.settings. 20 | * 21 | *

The BaseVersionFragment extends Fragment and provides a layout containing views to display 22 | * version-related information. Subclasses should override onViewCreated() to customize the 23 | * information displayed. 24 | * 25 | *

This class uses FragmentUpdaterBinding to inflate the layout, and it sets the version tag 26 | * and channel information using the CurrentVersion and VersionUtil classes. 27 | */ 28 | public class BaseVersionFragment extends Fragment { 29 | 30 | protected FragmentUpdaterBinding binding; 31 | 32 | @Override 33 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 34 | binding = FragmentUpdaterBinding.inflate(inflater, container, false); 35 | 36 | return binding.getRoot(); 37 | } 38 | 39 | @Override 40 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 41 | super.onViewCreated(view, savedInstanceState); 42 | 43 | binding.build.setInformationText(CurrentVersion.getInstance().getVersionTag()); 44 | binding.channel.setInformationText(VersionUtil.getChannelName(CurrentVersion.getInstance().getChannel(), requireContext())); 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/viewmodels/HighlightsViewModel.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.viewmodels; 2 | 3 | import static androidx.lifecycle.SavedStateHandleSupport.createSavedStateHandle; 4 | 5 | import androidx.lifecycle.LiveData; 6 | import androidx.lifecycle.MediatorLiveData; 7 | import androidx.lifecycle.SavedStateHandle; 8 | import androidx.lifecycle.ViewModel; 9 | import androidx.lifecycle.viewmodel.ViewModelInitializer; 10 | 11 | import java.util.List; 12 | 13 | import de.davis.passwordmanager.database.SecureElementManager; 14 | import de.davis.passwordmanager.database.dtos.SecureElement; 15 | 16 | public class HighlightsViewModel extends ViewModel { 17 | 18 | private static final String STATE = "state"; 19 | 20 | private final SavedStateHandle savedStateHandle; 21 | 22 | private final MediatorLiveData> elements = new MediatorLiveData<>(); 23 | 24 | public HighlightsViewModel(SavedStateHandle savedStateHandle) { 25 | this.savedStateHandle = savedStateHandle; 26 | 27 | elements.addSource(savedStateHandle.getLiveData(STATE, true), last ->{ 28 | if(last) 29 | elements.postValue(SecureElementManager.getLastCreatedSync(5)); 30 | else 31 | elements.postValue(SecureElementManager.getLastModifiedSync(5)); 32 | }); 33 | } 34 | 35 | public void setState(boolean lastAdded){ 36 | savedStateHandle.set(STATE, lastAdded); 37 | } 38 | 39 | public LiveData> getElements() { 40 | return elements; 41 | } 42 | 43 | public List getFavorites() { 44 | return SecureElementManager.getFavoritesSync(10); 45 | } 46 | 47 | public static final ViewModelInitializer initializer = new ViewModelInitializer<>(HighlightsViewModel.class, creationExtras -> 48 | new HighlightsViewModel(createSavedStateHandle(creationExtras))); 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/viewmodels/ScrollingViewModel.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.viewmodels; 2 | 3 | import androidx.lifecycle.LiveData; 4 | import androidx.lifecycle.MutableLiveData; 5 | import androidx.lifecycle.ViewModel; 6 | 7 | public class ScrollingViewModel extends ViewModel { 8 | 9 | private final MutableLiveData consumedY = new MutableLiveData<>(); 10 | private final MutableLiveData fabVisibility = new MutableLiveData<>(); 11 | 12 | public void setConsumedY(int consumed){ 13 | consumedY.setValue(consumed); 14 | } 15 | 16 | public void setFabVisibility(boolean visible){ 17 | fabVisibility.setValue(visible); 18 | } 19 | 20 | public LiveData getConsumedY() { 21 | return consumedY; 22 | } 23 | 24 | public MutableLiveData getFabVisibility() { 25 | return fabVisibility; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/views/AddBottomSheet.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.views; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | 11 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment; 12 | 13 | import de.davis.passwordmanager.database.ElementType; 14 | import de.davis.passwordmanager.databinding.AddBottomSheetContentBinding; 15 | import de.davis.passwordmanager.manager.ActivityResultManager; 16 | import de.davis.passwordmanager.ui.dashboard.DashboardFragment; 17 | 18 | public class AddBottomSheet extends BottomSheetDialogFragment { 19 | 20 | @Nullable 21 | @Override 22 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 23 | AddBottomSheetContentBinding binding = AddBottomSheetContentBinding.inflate(inflater, container, false); 24 | 25 | for (ElementType type : ElementType.values()){ 26 | AddButton btn = new AddButton(requireContext()); 27 | btn.setText(type.getTitle()); 28 | btn.setIcon(type.getIcon()); 29 | btn.setOnClickListener(v -> { 30 | ActivityResultManager.getOrCreateManager(DashboardFragment.class, null).launchCreate(type.getCreateActivityClass(), requireContext()); 31 | dismiss(); 32 | }); 33 | 34 | binding.linearLayout.addView(btn); 35 | } 36 | 37 | return binding.getRoot(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/views/AddButton.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.views; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.util.TypedValue; 6 | import android.view.Gravity; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | import androidx.appcompat.widget.AppCompatTextView; 11 | 12 | import de.davis.passwordmanager.utils.ResUtil; 13 | 14 | public class AddButton extends AppCompatTextView { 15 | 16 | public AddButton(@NonNull Context context) { 17 | super(context); 18 | init(); 19 | } 20 | 21 | public AddButton(@NonNull Context context, @Nullable AttributeSet attrs) { 22 | super(context, attrs); 23 | init(); 24 | } 25 | 26 | public AddButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 27 | super(context, attrs, defStyleAttr); 28 | init(); 29 | } 30 | 31 | private void init(){ 32 | setBackgroundResource(ResUtil.resolveAttribute(getContext().getTheme(), androidx.appcompat.R.attr.selectableItemBackground)); 33 | 34 | int dp = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, getContext().getResources().getDisplayMetrics()); 35 | 36 | setClickable(true); 37 | setFocusable(true); 38 | setPadding(dp, dp, dp, dp); 39 | setHeight((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 65, getContext().getResources().getDisplayMetrics())); 40 | 41 | setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); 42 | setGravity(Gravity.CENTER_VERTICAL); 43 | 44 | setCompoundDrawablePadding(dp/2); 45 | } 46 | 47 | public void setIcon(int id){ 48 | setCompoundDrawablesWithIntrinsicBounds(id, 0, 0, 0); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/views/ChangePasswordPreference.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.views 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import androidx.preference.Preference 6 | import de.davis.passwordmanager.R 7 | import de.davis.passwordmanager.ui.auth.AuthenticationRequest 8 | import de.davis.passwordmanager.ui.auth.RequestType 9 | import de.davis.passwordmanager.ui.auth.createRequestAuthenticationIntent 10 | 11 | class ChangePasswordPreference(context: Context, attributeSet: AttributeSet?) : 12 | Preference(context, attributeSet) { 13 | 14 | init { 15 | setTitle(R.string.update_main_password_info) 16 | intent = context.createRequestAuthenticationIntent( 17 | AuthenticationRequest.Builder(RequestType.AUTH_REQUEST_CHANGE_MAIN_PASSWORD).let { 18 | it.message = R.string.update_main_password_info 19 | it.build() 20 | } 21 | ) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/views/CheckableImageButton.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.views; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.graphics.drawable.Drawable; 6 | import android.util.AttributeSet; 7 | import android.widget.Checkable; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | import androidx.appcompat.content.res.AppCompatResources; 12 | import androidx.appcompat.widget.AppCompatImageButton; 13 | 14 | import com.google.android.material.color.MaterialColors; 15 | 16 | import de.davis.passwordmanager.R; 17 | 18 | public class CheckableImageButton extends AppCompatImageButton implements Checkable { 19 | 20 | private boolean checked; 21 | 22 | public CheckableImageButton(@NonNull Context context) { 23 | super(context); 24 | init(); 25 | } 26 | 27 | public CheckableImageButton(@NonNull Context context, @Nullable AttributeSet attrs) { 28 | super(context, attrs); 29 | init(); 30 | } 31 | 32 | public CheckableImageButton(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 33 | super(context, attrs, defStyleAttr); 34 | init(); 35 | } 36 | 37 | private void init(){ 38 | Drawable drawable = AppCompatResources.getDrawable(getContext(), R.drawable.password_eye); 39 | if(drawable == null) 40 | return; 41 | 42 | drawable.setTint(MaterialColors.getColor(getContext(), com.google.android.material.R.attr.colorOnSurfaceVariant, Color.BLACK)); 43 | setImageDrawable(drawable); 44 | } 45 | 46 | @Override 47 | public void setChecked(boolean checked) { 48 | if(this.checked == checked) 49 | return; 50 | 51 | this.checked = checked; 52 | setImageState(new int[]{android.R.attr.state_checked * (checked ? 1 : -1)}, true); 53 | } 54 | 55 | @Override 56 | public boolean isChecked() { 57 | return checked; 58 | } 59 | 60 | @Override 61 | public void toggle() { 62 | setChecked(!isChecked()); 63 | } 64 | 65 | @Override 66 | public boolean performClick() { 67 | toggle(); 68 | return super.performClick(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/ui/views/copy/CopyView.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.ui.views.copy; 2 | 3 | public interface CopyView { 4 | 5 | String getCopyString(); 6 | void setCopyable(boolean copyable); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/AssetsUtil.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStreamReader; 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class AssetsUtil { 13 | 14 | public static List open(String filename, Context context) throws IOException { 15 | BufferedReader reader = new BufferedReader(new InputStreamReader(context.getAssets().open(filename), StandardCharsets.UTF_8)); 16 | List result = new ArrayList<>(); 17 | for (;;) { 18 | String line = reader.readLine(); 19 | if (line == null) 20 | break; 21 | 22 | if(line.startsWith("#")) 23 | continue; 24 | 25 | result.add(line); 26 | } 27 | return result; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/BrowserUtil.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils; 2 | 3 | import android.content.Context; 4 | import android.net.Uri; 5 | import android.webkit.URLUtil; 6 | import android.widget.Toast; 7 | 8 | import androidx.browser.customtabs.CustomTabsIntent; 9 | 10 | import java.net.URL; 11 | import java.util.regex.Pattern; 12 | 13 | import de.davis.passwordmanager.R; 14 | import kotlin.text.Regex; 15 | import kotlin.text.RegexOption; 16 | 17 | public class BrowserUtil { 18 | 19 | public static String ensureProtocol(String url){ 20 | if(url == null) 21 | return null; 22 | 23 | if(url.matches("^(http(s?))://(.*)")) 24 | return url; 25 | 26 | return "https://"+ url; 27 | } 28 | 29 | public static Boolean couldBeUrl(String toCheck, String actualUrl){ 30 | if (toCheck.isBlank()) return false; 31 | String titleWithDots = Pattern.quote(toCheck.replace(" ", ".")); 32 | String titleWithoutSpaces = Pattern.quote(toCheck.replace(" ", "")); 33 | 34 | String regexPattern = "("+ titleWithDots +"|"+ titleWithoutSpaces +")"; 35 | try { 36 | URL url = new URL(actualUrl); 37 | String domain = url.getHost(); 38 | 39 | return new Regex(regexPattern, RegexOption.IGNORE_CASE).containsMatchIn(domain) || 40 | new Regex(regexPattern, RegexOption.IGNORE_CASE).containsMatchIn(domain.replace(".", "")); 41 | } catch (Exception e) { 42 | return false; 43 | } 44 | } 45 | 46 | public static void open(String url, Context context){ 47 | url = ensureProtocol(url); 48 | 49 | if(!isValidURL(url)){ 50 | Toast.makeText(context, R.string.invalid_url, Toast.LENGTH_SHORT).show(); 51 | return; 52 | } 53 | 54 | CustomTabsIntent intent = new CustomTabsIntent.Builder().setShowTitle(true).build(); 55 | intent.intent.putExtra("com.google.android.apps.chrome.EXTRA_OPEN_NEW_INCOGNITO_TAB", true); 56 | intent.launchUrl(context, Uri.parse(url)); 57 | } 58 | 59 | public static boolean isValidURL(String url) { 60 | return URLUtil.isValidUrl(url) && url.contains("."); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/CreditCardUtil.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.time.YearMonth; 5 | import java.time.format.DateTimeFormatter; 6 | import java.util.Date; 7 | import java.util.Locale; 8 | 9 | import de.davis.passwordmanager.utils.card.Card; 10 | import de.davis.passwordmanager.utils.card.CardFactory; 11 | 12 | public class CreditCardUtil { 13 | 14 | public static boolean isValidDateFormat(String formatted){ 15 | if(formatted == null) 16 | return false; 17 | 18 | try{ 19 | YearMonth.parse(formatted, DateTimeFormatter.ofPattern("MM/yy")); 20 | return true; 21 | }catch (Exception e){ 22 | return false; 23 | } 24 | } 25 | 26 | public static boolean isValidCardNumberLength(String cardNumber){ 27 | if(cardNumber == null) 28 | return false; 29 | 30 | Card card = CardFactory.INSTANCE.createFromCardNumber(cardNumber); 31 | 32 | return card.isValidLength(); 33 | } 34 | 35 | public static boolean isValidCheckSum(String cardNumber){ 36 | if(cardNumber == null) 37 | return false; 38 | 39 | Card card = CardFactory.INSTANCE.createFromCardNumber(cardNumber); 40 | 41 | return card.isValidLuhnNumber(); 42 | } 43 | 44 | public static String formatNumber(String s){ 45 | Card card = CardFactory.INSTANCE.createFromCardNumber(s); 46 | 47 | return card.getCardNumber(); 48 | } 49 | 50 | public static String formatDate(Date date){ 51 | SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM/yy", Locale.getDefault()); 52 | return simpleDateFormat.format(date); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/KeyUtil.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils; 2 | 3 | import android.security.keystore.KeyGenParameterSpec; 4 | import android.security.keystore.KeyProperties; 5 | 6 | import java.io.IOException; 7 | import java.security.GeneralSecurityException; 8 | import java.security.KeyStore; 9 | import java.security.NoSuchAlgorithmException; 10 | 11 | import javax.crypto.Cipher; 12 | import javax.crypto.KeyGenerator; 13 | import javax.crypto.NoSuchPaddingException; 14 | import javax.crypto.SecretKey; 15 | 16 | public class KeyUtil { 17 | 18 | public static final String KEY_ALIAS = "password_manager_skey"; 19 | 20 | 21 | public static SecretKey generateSecretKey(KeyGenParameterSpec keyGenParameterSpec) throws GeneralSecurityException { 22 | KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); 23 | keyGenerator.init(keyGenParameterSpec); 24 | return keyGenerator.generateKey(); 25 | } 26 | 27 | public static SecretKey getSecretKey() throws GeneralSecurityException, IOException { 28 | KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore"); 29 | keyStore.load(null); 30 | SecretKey key = (SecretKey) keyStore.getKey(KEY_ALIAS, null); 31 | if(key == null) 32 | return generateSecretKey(new KeyGenParameterSpec.Builder( 33 | KEY_ALIAS,KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) 34 | .setBlockModes(KeyProperties.BLOCK_MODE_GCM) 35 | .setKeySize(256) 36 | .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE).build()); 37 | 38 | return key; 39 | } 40 | 41 | public static Cipher getCipher() { 42 | try { 43 | return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" 44 | + KeyProperties.BLOCK_MODE_GCM + "/" 45 | + KeyProperties.ENCRYPTION_PADDING_NONE); 46 | } catch (NoSuchAlgorithmException | NoSuchPaddingException ex) { 47 | RuntimeException e = new RuntimeException(); 48 | e.addSuppressed(ex); 49 | throw e; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/PreferenceUtil.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import androidx.annotation.StringRes; 7 | import androidx.preference.PreferenceManager; 8 | 9 | import de.davis.passwordmanager.R; 10 | import de.davis.passwordmanager.ui.settings.BaseSettingsFragment; 11 | import de.davis.passwordmanager.version.Version; 12 | 13 | public class PreferenceUtil { 14 | 15 | public static boolean getBoolean(Context context, @StringRes int key, boolean defValue){ 16 | return getPreferences(context).getBoolean(context.getString(key), defValue); 17 | } 18 | 19 | public static boolean putBoolean(Context context, @StringRes int key, boolean value){ 20 | return getPreferences(context).edit().putBoolean(context.getString(key), value).commit(); 21 | } 22 | 23 | public static SharedPreferences getPreferences(Context context){ 24 | return PreferenceManager.getDefaultSharedPreferences(context); 25 | } 26 | 27 | public static long getTimeForNewAuthentication(Context context){ 28 | return BaseSettingsFragment.getTime(getPreferences(context).getInt(context.getString(R.string.preference_reauthenticate), 5)); 29 | } 30 | 31 | @Version.Channel 32 | public static int getUpdateChannel(Context context){ 33 | return getPreferences(context).getInt("update_channel", Version.CHANNEL_STABLE); 34 | } 35 | 36 | public static void putUpdateChannel(Context context, @Version.Channel int channel){ 37 | getPreferences(context).edit().putInt("update_channel", channel).apply(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/ResUtil.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils; 2 | 3 | import android.content.res.Resources; 4 | import android.util.TypedValue; 5 | 6 | import androidx.annotation.AnyRes; 7 | 8 | public class ResUtil { 9 | 10 | @AnyRes 11 | public static int resolveAttribute(Resources.Theme theme, int resId){ 12 | TypedValue typedValue = new TypedValue(); 13 | theme.resolveAttribute(resId, typedValue, true); 14 | return typedValue.resourceId; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/TimeoutUtil.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils; 2 | 3 | public class TimeoutUtil { 4 | 5 | private long lastTime; 6 | private long diff; 7 | 8 | private long currentTimeMillis; 9 | 10 | public void initiateDelay() { 11 | currentTimeMillis = System.currentTimeMillis(); 12 | if(lastTime == 0) 13 | lastTime = currentTimeMillis; 14 | diff = currentTimeMillis - lastTime; 15 | } 16 | 17 | public boolean delayMet(long timeout){ 18 | boolean met = diff >= timeout; 19 | 20 | if(met) 21 | lastTime = currentTimeMillis; 22 | 23 | return met; 24 | } 25 | 26 | public boolean hasDelayMet(long timeout){ 27 | long currentTimeMillis = System.currentTimeMillis(); 28 | boolean met = currentTimeMillis - lastTime >= timeout; 29 | if(met) 30 | lastTime = currentTimeMillis; 31 | 32 | return met; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/VersionUtil.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils; 2 | 3 | import static de.davis.passwordmanager.version.Version.CHANNEL_ALPHA; 4 | import static de.davis.passwordmanager.version.Version.CHANNEL_BETA; 5 | import static de.davis.passwordmanager.version.Version.CHANNEL_RC; 6 | import static de.davis.passwordmanager.version.Version.CHANNEL_STABLE; 7 | import static de.davis.passwordmanager.version.Version.CHANNEL_UNKNOWN; 8 | 9 | import android.content.Context; 10 | 11 | import org.apache.commons.lang3.ArrayUtils; 12 | 13 | import de.davis.passwordmanager.R; 14 | import de.davis.passwordmanager.version.Version; 15 | 16 | public class VersionUtil { 17 | 18 | private static final int[] CHANNELS = {CHANNEL_STABLE, CHANNEL_RC, CHANNEL_BETA, CHANNEL_ALPHA}; 19 | 20 | public static String getChannelName(@Version.Channel int channelType, Context context){ 21 | if(channelType == CHANNEL_UNKNOWN) 22 | return "unknown"; 23 | 24 | String[] array = context.getResources().getStringArray(R.array.update_channels); 25 | return array[ArrayUtils.indexOf(CHANNELS, channelType)]; 26 | } 27 | 28 | @Version.Channel 29 | public static int getChannelByName(String channelName, Context context){ 30 | String[] array = context.getResources().getStringArray(R.array.update_channels); 31 | return CHANNELS[ArrayUtils.indexOf(array, channelName)]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/card/Card.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils.card 2 | 3 | import de.davis.passwordmanager.utils.card.algorithm.LuhnAlgorithm 4 | 5 | class Card(val rawNumber: String, val type: CardType) { 6 | val cardNumber: String = type.formatter.format(rawNumber) 7 | 8 | fun mask() = type.formatter.format(rawNumber.replace("\\d(?=\\d{4})".toRegex(), "•")) 9 | 10 | fun isValidLuhnNumber(): Boolean = LuhnAlgorithm.isValid(rawNumber) 11 | 12 | fun isValidLength(): Boolean = type.lengthRange.contains(rawNumber.length) 13 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/card/CardFactory.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils.card 2 | 3 | object CardFactory { 4 | fun createFromCardNumber(cardNumber: String): Card { 5 | return Card(cardNumber.replace(" ", ""), CardType.getTypeByNumber(cardNumber)) 6 | } 7 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/card/CardType.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils.card 2 | 3 | enum class CardType( 4 | val prefixes: List, 5 | val lengthRange: IntRange, 6 | val formatter: Formatter 7 | ) { 8 | Visa(listOf("4"), 16..16, Formatter.FourDigitChunkFormatter), 9 | MasterCard((1..5).map { "5$it" }, 16..16, Formatter.FourDigitChunkFormatter), 10 | AmericanExpress(listOf("34", "37"), 15..15, Formatter.FourSixRemainderChunkFormatter), 11 | Discover(listOf("6011", "65"), 16..16, Formatter.FourDigitChunkFormatter), 12 | JCB(listOf("35"), 16..19, Formatter.FourDigitChunkFormatter), 13 | DinnersClub(listOf("36", "38", "39"), 14..14, Formatter.FourSixRemainderChunkFormatter), 14 | 15 | Unknown(emptyList(), 14..19, Formatter.FourDigitChunkFormatter); 16 | 17 | companion object { 18 | fun getTypeByNumber(cardNumber: String) = entries.firstOrNull { 19 | it.prefixes.any { prefix -> cardNumber.startsWith(prefix) } 20 | } ?: Unknown 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/card/Formatter.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils.card 2 | 3 | sealed class Formatter { 4 | 5 | data object FourDigitChunkFormatter : Formatter() { 6 | 7 | override fun runFormat(cardNumber: String): String = cardNumber.chunked(4).joinToString(" ") 8 | } 9 | 10 | 11 | data object FourSixRemainderChunkFormatter : Formatter() { 12 | 13 | override fun runFormat(cardNumber: String): String = when { 14 | cardNumber.length <= 4 -> cardNumber 15 | cardNumber.length <= 10 -> cardNumber.take(4) + " " + cardNumber.substring(4) 16 | else -> cardNumber.substring(0, 4) + " " + cardNumber.substring(4, 10) + 17 | " " + cardNumber.substring(10) 18 | } 19 | } 20 | 21 | protected abstract fun runFormat(cardNumber: String): String 22 | 23 | fun format(cardNumber: String): String { 24 | return runFormat(cardNumber.replace(" ", "")) 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/utils/card/algorithm/LuhnAlgorithm.kt: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.utils.card.algorithm 2 | 3 | object LuhnAlgorithm { 4 | 5 | fun isValid(cardNumber: String): Boolean { 6 | val sanitizedNumber = cardNumber.filter { it.isDigit() } 7 | return sanitizedNumber.reversed().mapIndexed { index, c -> 8 | val digit = c.digitToInt() 9 | if (index % 2 == 1) (digit * 2).let { if (it > 9) it - 9 else it } else digit 10 | }.sum() % 10 == 0 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/de/davis/passwordmanager/version/CurrentVersion.java: -------------------------------------------------------------------------------- 1 | package de.davis.passwordmanager.version; 2 | 3 | import java.io.Serial; 4 | import java.util.regex.Matcher; 5 | import java.util.regex.Pattern; 6 | 7 | import de.davis.passwordmanager.BuildConfig; 8 | 9 | public class CurrentVersion extends Version{ 10 | 11 | @Serial 12 | private static final long serialVersionUID = -4800867108941482255L; 13 | private static CurrentVersion instance; 14 | 15 | private static final String REGEX = "^(\\d+\\.\\d+\\.\\d+(?:-\\w+\\d+)?)(?:-(?!debug)(\\w+))?(-debug)?$"; 16 | 17 | private final String additionalTag; 18 | 19 | private final boolean debug; 20 | 21 | private CurrentVersion(String vTag, String additionalTag, boolean debug) { 22 | super(fromVersionTag(vTag)); 23 | this.debug = debug; 24 | this.additionalTag = additionalTag; 25 | } 26 | 27 | @Override 28 | public int getChannel() { 29 | return debug ? CHANNEL_UNKNOWN : super.getChannel(); 30 | } 31 | 32 | @Override 33 | public String getVersionTag() { 34 | return super.getVersionTag() + (additionalTag == null ? "" : "-"+ additionalTag); 35 | } 36 | 37 | public static CurrentVersion getInstance(){ 38 | if(instance == null) { 39 | Pattern pattern = Pattern.compile(REGEX); 40 | Matcher matcher = pattern.matcher(BuildConfig.VERSION_NAME); 41 | if(!matcher.matches()) 42 | return new CurrentVersion("", null, BuildConfig.DEBUG); 43 | 44 | String version = "v"+ matcher.group(1); 45 | String additionalTag = matcher.group(2); 46 | String debug = matcher.group(3); 47 | 48 | instance = new CurrentVersion(version, additionalTag, debug != null); 49 | } 50 | 51 | return instance; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/proto/user_main_password.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/protobuf/timestamp.proto"; 4 | 5 | option java_package = "de.davis.passwordmanager.security.mainpassword"; 6 | option java_multiple_files = true; 7 | 8 | message UserMainPassword { 9 | string hexHash = 1; 10 | google.protobuf.Timestamp created_at = 2; 11 | } -------------------------------------------------------------------------------- /app/src/main/res/color/chip_background_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_bug_report_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_filter_list_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_settings_backup_restore_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_star_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_star_outline_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_build_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_calendar_month_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_check_24.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_check_circle_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_close_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_code_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_contactless_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_credit_card_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_dashboard_24.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_delete_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_edit_24.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_location_on_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_more_vert_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_numbers_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_open_in_new_24.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_password_24.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_person_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_refresh_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_security_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_settings_24.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_title_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/password_eye.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/pb_design_ic_visibility.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 25 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/pb_design_ic_visibility_off.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 25 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout-sw600dp/generate_radio_btn_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 14 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout-w600dp/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | 23 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_authentication.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_import.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 18 | 19 |