├── .gitignore ├── README.md ├── assets └── store │ ├── pwm-feature.jpg │ ├── pwm-promo-graphic.png │ ├── pwm-ring.png │ └── store-listing.txt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── license.txt ├── passwordmaker ├── .gitignore ├── build.gradle ├── libs │ └── tasermonkeys-gjson-1.7.1.jar ├── lint.xml ├── proguard-rules.txt ├── set_signing_env_vars.sh └── src │ ├── androidTest │ ├── java │ │ └── org │ │ │ └── passwordmaker │ │ │ ├── PwmTest.java │ │ │ └── TestUtils.java │ └── resources │ │ └── test.xml │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── org │ │ └── passwordmaker │ │ ├── AccountManagerSamples.java │ │ └── android │ │ ├── AccountDetailActivity.java │ │ ├── AccountDetailFragment.java │ │ ├── AccountListActivity.java │ │ ├── AccountListFragment.java │ │ ├── AlgorithmSelectionValues.java │ │ ├── AndroidGlobalSettings.java │ │ ├── AndroidRDFDatabaseWriter.java │ │ ├── ClassicSettingsImporter.java │ │ ├── EditFavoritesActivity.java │ │ ├── EditFavoritesFragment.java │ │ ├── ImportExportRdf.java │ │ ├── Logtags.java │ │ ├── MainActivity.java │ │ ├── PatternDataDetailActivity.java │ │ ├── PatternDataDetailFragment.java │ │ ├── PatternDataListActivity.java │ │ ├── PatternDataListFragment.java │ │ ├── PwmApplication.java │ │ ├── SettingsActivity.java │ │ ├── TextWatcherAdapter.java │ │ ├── adapters │ │ └── SubstringArrayAdapter.java │ │ ├── preferences │ │ ├── MasterPasswordPreference.java │ │ └── SettingsFragment.java │ │ ├── widgets │ │ └── SwipeDismissListViewTouchListener.java │ │ └── xmlwrappers │ │ └── AndroidXmlStreamWriter.java │ └── res │ ├── drawable-hdpi │ ├── ic_action_av_add_to_queue.png │ ├── ic_action_collections_labels.png │ ├── ic_action_content_import_export.png │ ├── ic_launcher.png │ └── ic_menu_copy_holo_light.png │ ├── drawable-mdpi │ ├── ic_action_av_add_to_queue.png │ ├── ic_action_collections_labels.png │ ├── ic_action_content_import_export.png │ ├── ic_launcher.png │ └── ic_menu_copy_holo_light.png │ ├── drawable-xhdpi │ ├── ic_action_av_add_to_queue.png │ ├── ic_action_collections_labels.png │ ├── ic_action_content_import_export.png │ ├── ic_launcher.png │ └── ic_menu_copy_holo_light.png │ ├── drawable-xxhdpi │ ├── ic_action_av_add_to_queue.png │ ├── ic_action_collections_labels.png │ ├── ic_action_content_import_export.png │ ├── ic_launcher.png │ └── ic_menu_copy_holo_light.png │ ├── drawable │ └── list_item_account_selector.xml │ ├── layout │ ├── activity_account_detail.xml │ ├── activity_account_list.xml │ ├── activity_account_twopane.xml │ ├── activity_edit_favorites.xml │ ├── activity_import_export_rdf.xml │ ├── activity_main.xml │ ├── activity_patterndata_detail.xml │ ├── activity_patterndata_list.xml │ ├── activity_patterndata_twopane.xml │ ├── dialog_set_pwd_hash.xml │ ├── fragment_account_detail.xml │ ├── fragment_account_list.xml │ ├── fragment_patterndata_detail.xml │ └── toolbar.xml │ ├── menu │ ├── account_list.xml │ ├── account_list_menu.xml │ ├── edit_favorites.xml │ ├── main.xml │ └── pattern_data_list.xml │ ├── values-large │ ├── refs.xml │ └── strings.xml │ ├── values-sw600dp │ └── refs.xml │ ├── values-w820dp │ └── dimens.xml │ ├── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml │ └── xml │ └── preferences.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | gen 2 | bin 3 | out 4 | build/ 5 | .classpath 6 | .settings 7 | .project 8 | .metadata 9 | .DS_Store 10 | .idea 11 | *.iml 12 | proguard_logs 13 | .gradle 14 | local.properties 15 | release/ 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Passwordmaker Pro for Android 2 | =========== 3 | 4 | This is the android implementation of the Passwordmaker Pro algorithm designed by [Passwordmaker.org](http://passwordmaker.org). View the [Android public webpage](http://android.passwordmaker.org). 5 | 6 | How it works: 7 | You provide PasswordMaker two pieces of information: a "master password" -- that one, single password you like -- and 8 | the URL of the website requiring a password. Through the magic of one-way hash algorithms, PasswordMaker calculates a 9 | message digest(hash), also known as a digital fingerprint, which can be used as your password for the website. 10 | Although one-way hash algorithms have a number of interesting characteristics, the one capitalized by PasswordMaker 11 | is that the resulting fingerprint (password) does "not reveal anything about the input that was used to generate it." 12 | In other words, if someone has one or more of your generated passwords, it is computationally infeasible for him 13 | to derive your master password or to calculate your other passwords. Computationally infeasible means even computers 14 | like this won't help! 15 | 16 | There are the same tools that you can download for many of the popular browsers, and other mobile devices. See the 17 | [Passwordmaker.org](http://passwordmaker.org) website for more information. 18 | 19 | For bug reports please see the [issue tracker](https://github.com/passwordmaker/android-passwordmaker/issues). 20 | 21 | Feel free to create a pull request to fix a bug or add a feature yourself! 22 | 23 | Users 24 | ====== 25 | Welcome to passwordmaker! If you haven't already checkout [Passwordmaker.org](http://passwordmaker.org) to know what password maker is all about. 26 | 27 | If you have an issues or questions, with the Android version of PasswordMaker Pro, go to the [Issue Tracker](https://github.com/passwordmaker/android-passwordmaker/issues) 28 | 29 | Have a comment, question, or issue that you want to say more privately not on the issue tracker, feel free to [email](mailto:pwdmkrpro.android.84a75@tasermonkeys.com) me. 30 | 31 | [Developer's keybase.io account](https://keybase.io/jstapleton) 32 | 33 | Developers 34 | ========== 35 | If you are not a developer you probably don't need to read any farther down this file. 36 | 37 | Repository layout 38 | ================== 39 | This project uses the [Git Flow](http://nvie.com/posts/a-successful-git-branching-model/) layout. You can download a helper plugin for git from https://github.com/nvie/gitflow repository. 40 | 41 | Compiling 42 | ========== 43 | * You will ofcourse need to download the [Android SDK](http://developer.android.com/sdk/index.html#download) 44 | * Download and install an IDE like [IntelliJ](http://www.jetbrains.com/idea/) with its plugin for Android. 45 | * Until [passwordmaker-je-lib](https://github.com/passwordmaker/java-passwordmaker-lib) makes it into maven central, check it out and do a mvn install on it first. 46 | 47 | I now set this up using the gradle build process. I use the Intellij IDE which makes the process really easy. Just 48 | install the Intellij Android plugin and import this project after cloning this repository. Use the import from external 49 | model, then choose "gradle". It should take care of the rest. Intellij created gradle wrappers which I believe you can 50 | just run `./gradlew ` 51 | 52 | See `./gradlew tasks` for all of the tasks you wish to do. 53 | 54 | For example to run the Android Test cases, run: `./gradlew connectedAndroidTest` 55 | 56 | Though I heavily suggest using an IDE like Intellij or Eclipse. 57 | 58 | Step by Step Compiling in the commandline 59 | =========== 60 | ### Step 1: get build, and install passwordmaker-je 61 | 62 | git clone https://github.com/tasermonkey/passwordmaker-je-lib.git 63 | cd passwordmaker-je-lib 64 | git checkout 0.9.3 65 | mvn install 66 | 67 | ### Step 2: get and build android passwordmaker 68 | 69 | # the cd .. is just to go to the same parent directory as the passwordmaker-je-lib to checkout the android code 70 | cd .. 71 | git clone https://github.com/tasermonkey/android-passwordmaker.git 72 | git checkout release/v2.0.0 73 | cd android-passwordmaker/passwordmaker 74 | ../gradlew assembleDebug 75 | 76 | ### Step 3: apk 77 | This should have built an apk inside of the android-passwordmaker/passwordmaker/build/apk directory: passwordmaker-debug.apk 78 | 79 | To use gradle to install this, run: 80 | 81 | ../gradlew installDebug 82 | 83 | To assemble the release mode, you run the task `assembleRelease` and to install: `installRelease`. However in order to build this you need to setup the signing. 84 | 85 | Signing 86 | ========== 87 | In order to build this project you need to setup signing. I can't just include the signing keys in the git repo because that would mean anyone could sign as me. 88 | So you will need to generate your own signing keys. See [Android signing help](http://developer.android.com/tools/publishing/app-signing.html) 89 | 90 | Then from the `android-passwordmaker/passwordmaker` you need to setup your Environment by running the script: 91 | 92 | source set_signing_env_vars.sh 93 | 94 | This script is required to be 'sourced' from your shell whenever you open up a new shell, and be source because it adds 4 environment variables to your session. 95 |
NOTE: This seems like a pretty decent way to do this, as the password will never be visible. Also never stored on disk. However, maybe somehow invent a gpg way of storing the password and config info. 96 | 97 | 98 | Notes 99 | ====== 100 | * Why does PasswordMakerPro require using `spongycastle`? 101 | - Because Android provides part of `BouncyCastle`, but not all of it. And some of the Hash algorithms used by passwordmaker didn't make the cut. 102 | - Spongycastle is basically just `BouncyCastle` moved to a different package. Then renamed to `SC` as its provider name. 103 | * Why did you need to Shim out the XmlWriter interface? 104 | - Because once against, Android moved out some of the `javax.xml.streaming.*` classes and it doesn't work. 105 | - So the solution was to just shim out the interface, then have it use the xmlpull package that comes with Android. 106 | * Why did you want to move to use the java library `passwordmaker-je`? 107 | - This way any improvements to the core can be made to both the standard `java edition` and the android edition! 108 | - See the Java library code repository here: [passwordmaker-je-lib](http://github.com/tasermonkey/passwordmaker-je-lib) 109 | - The original source came from: http://code.google.com/p/passwordmaker-je/ 110 | - Thanks to Dave Marotti for this 111 | * Version Code strategy: 112 | - `..` -> code: First digit is major, second two digits is minor, and last two digits are revision. 113 | - Example: for `2.13.4` the code would be `21304` This way as the version number increases the code will also be a bigger number 114 | - This is different for the first 10 released versions which were just the release numbers. 115 | * What the blank, mvn and gradle? 116 | - Yeah, until I fixed up the build for the passwordmaker-je-lib more, it is using maven. 117 | - And this project is using gradle, cause really, I like gradle, other than its slow to startup. But it works well with android dev. 118 | 119 | Deploying using the android developer webpage and Android Studio 120 | ======================= 121 | 1. In Android Studio go to action: `Generate Signed Bundled` 122 | 1. Choose `Android App Bundle` 123 | 1. Locate keystore, select alias `passwordmakerproforandroid` and type in the correct password. 124 | 1. Select destination folder and use build type `releases` 125 | 1. Locate the release bundle (Android Studio should prompt for it): something named `passwordmaker.aab` 126 | 1. Log into Android developer Play console: https://play.google.com/apps/publish 127 | 1. Within the application for passwordmaker go to the `Release management` / `App Releases` 128 | 1. Select `Manage` from the `Production` track 129 | 1. Click on the button `Create Release` 130 | 1. Give it the signed bundle generated. 131 | 1. Ensure the internal version is reasonable. 132 | 1. give a short description on what changed 133 | 1. Click Save, followed by 'Review' 134 | 1. Let it get deployed. 135 | 136 | Adding the built artifacts to releases in Github 137 | ======================= 138 | - As a place to keep all of the versions of the software, its nice to use the releases section of github. 139 | - Should now release both the aab and the apk. 140 | - In Android studio go to build/'Generate signed bundled/apk' 141 | - We will do both options, one at a time (Android App Bundle and then APK) 142 | - If you followed the instructions to upload to the Play Store, you already did the signed app bundled. 143 | - So just go throught the Build signed 'APK' option 144 | - Locate keystore, select alias `passwordmakerproforandroid` and type in the correct password. 145 | - Select destination folder and use build type `releases` 146 | - Ensure both v1 and v2 `Signature Versions` are selected. 147 | - Click finished. 148 | - Find the files in the 'releases' directory and upload it as the release for the new tagged version. 149 | 150 | 151 | Status of this project 152 | ======================= 153 | 154 | ## Update 2014-07-17 by @tasermonkey 155 | 156 | Release [Version 2.0.1](https://github.com/passwordmaker/android-passwordmaker/releases/tag/v2.0.1) 157 | 158 | # Note on Patches/Pull Requests: 159 | 160 | * Fork the project. 161 | * Make your feature addition or bug fix. 162 | * Commit, do not mess with version in build.gradle 163 | (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) 164 | * Send me a pull request. Bonus points for feature branches. 165 | 166 | Copyright 167 | ========== 168 | 169 | Copyright (c) 2010 [James Stapleton](https://keybase.io/jstapleton) and [PasswordMaker.org](http://passwordmaker.org) . See LICENSE for details. A list of all contributors can be found at the [contributors](http://github.com/passwordmaker/android-passwordmaker/contributors) page. 170 | -------------------------------------------------------------------------------- /assets/store/pwm-feature.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/assets/store/pwm-feature.jpg -------------------------------------------------------------------------------- /assets/store/pwm-promo-graphic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/assets/store/pwm-promo-graphic.png -------------------------------------------------------------------------------- /assets/store/pwm-ring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/assets/store/pwm-ring.png -------------------------------------------------------------------------------- /assets/store/store-listing.txt: -------------------------------------------------------------------------------- 1 | #Title 2 | PasswordMaker Pro 3 | 4 | #Short Description 5 | PasswordMaker creates unique, secure passwords that are not needed to be saved. 6 | 7 | #Full Description 8 | ONE PASSWORD TO RULE THEM ALL 9 | 10 | PasswordMaker creates unique, secure passwords that are very easy for you to retrieve but no one else. Nothing is stored anywhere, anytime, so there's nothing to be hacked, lost, or stolen. 11 | 12 | You provide PasswordMaker two pieces of information: a "master password" -- that one, single password you like -- and the URL of the website requiring a password. PasswordMaker will then generate a unique password using a one-way hash, which will protect your master password. 13 | 14 | Compatible with the other versions of Passwordmaker for other devices and computers. 15 | 16 | See http://android.passwordmaker.org/ for more information on PasswordMaker. 17 | 18 | If you have questions, comments or concerns please post them on them on the github issue tracker by following the link above. I do not monitor the ratings and comments here. Also go to the page: http://passwordmaker.org to find other versions for other platforms like the chrome or firefox versions of passwordmaker. 19 | 20 | # Categorization 21 | Application Type: Applications 22 | Category: Tools 23 | Content rating: Everyone 24 | Website: http://android.passwordmaker.org 25 | Email: pwmp.for.android@gmail.com -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.3.1' 11 | 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | mavenLocal() 21 | google() 22 | jcenter() 23 | mavenCentral() 24 | } 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Feb 11 20:01:53 EST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /passwordmaker/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /passwordmaker/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | 4 | /* 5 | * Gets the version name from the latest Git tag 6 | * 7 | * Tag each release like: git tag -a v1.0 8 | * then Rebuild for a nice version number 9 | * 10 | */ 11 | def generateVersionName = { -> 12 | def stdout = new ByteArrayOutputStream() 13 | exec { 14 | commandLine 'git', 'describe', '--tags' 15 | standardOutput = stdout 16 | } 17 | String output = stdout.toString() 18 | if (output.startsWith('v')) output = output[1..-1] 19 | output = output.trim() 20 | return output 21 | } 22 | 23 | def generateVersionCode = { -> 24 | def versions = generateVersionName().split("-", 4) 25 | def code = versions[0].replaceAll("-.*", "") 26 | .split("\\.").collect { 27 | it.padLeft(2, "0") 28 | }.join("") 29 | if ( versions.size() >= 2 && versions[1].isNumber() ) { 30 | // This is off of a release tag: '2.0.5' but has a couple of commits since: 2.0.5-4-g6af5805 we want the code to have '04' in this case. 31 | code += versions[1].padLeft(2, "0") 32 | } else if ( versions.size() >= 3 && versions[2].isNumber() ) { 33 | // This has a '-qualifier-##' e.g. '2.0.5-SNAPSHOT-1-g6af5805' we want the code to have '01' appended in this case. 34 | code += versions[2].padLeft(2, "0") 35 | } else if ( versions[-1] == "SNAPSHOT") { 36 | // The first snapshot 37 | code += "00" 38 | } else { 39 | // This is if we are on the release tag itself, lets force this to be the end of the revisions 40 | // example: '2.0.5' as the tag, we want to add 99 to the end, so that any revisions handed out during testing will be 41 | // less than this revision. 42 | code += "99" 43 | } 44 | return code.toInteger() 45 | } 46 | 47 | apply plugin: 'com.android.application' 48 | 49 | android { 50 | compileSdkVersion 28 51 | defaultConfig { 52 | applicationId "org.passwordmaker.android" 53 | minSdkVersion 15 //2018-12-08: 99% of android users are using Sdk Version 15+. Too lazy to deal with upgrading anyways. 54 | targetSdkVersion 28 55 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 56 | versionCode generateVersionCode() 57 | versionName generateVersionName() 58 | println "Building version ${getVersionName()} (${getVersionCode()})" 59 | } 60 | buildTypes { 61 | release { 62 | minifyEnabled false 63 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 64 | } 65 | } 66 | 67 | signingConfigs { 68 | release { 69 | def env = System.getenv() 70 | if (env['PASSWORDMAKER_KEYSTORE_FILE'] != null) { 71 | storeFile file(env['PASSWORDMAKER_KEYSTORE_FILE']) 72 | storePassword env['PASSWORDMAKER_KEYSTORE_PASSWORD'] 73 | keyAlias env['PASSWORDMAKER_KEYSTORE_KEY_ALIAS'] 74 | keyPassword env['PASSWORDMAKER_KEYSTORE_KEY_PASSWORD'] 75 | println ("Using keystore file: ${file(env['PASSWORDMAKER_KEYSTORE_FILE'])}") 76 | } else { 77 | println ("##################") 78 | println ("No keystore for release set.") 79 | println ("Set PASSWORDMAKER_KEYSTORE_FILE, PASSWORDMAKER_KEYSTORE_PASSWORD," 80 | + "PASSWORDMAKER_KEYSTORE_KEY_ALIAS and PASSWORDMAKER_KEYSTORE_KEY_PASSWORD") 81 | println ("##################") 82 | } 83 | } 84 | } 85 | } 86 | 87 | dependencies { 88 | implementation fileTree(dir: 'libs', include: ['*.jar']) 89 | // You must install or update the Support Repository through the SDK manager to use this dependency. 90 | implementation 'com.android.support:appcompat-v7:28.0.0' 91 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 92 | implementation 'com.android.support:design:28.0.0' 93 | implementation 'com.intellij:annotations:12.0@jar' 94 | implementation 'org.passwordmaker:passwordmaker-je-lib:0.9.10' 95 | implementation 'com.madgag.spongycastle:core:1.50.0.0' 96 | implementation 'com.madgag.spongycastle:prov:1.50.0.0' 97 | implementation 'com.google.guava:guava:27.0.1-android' 98 | testImplementation 'junit:junit:4.12' 99 | } -------------------------------------------------------------------------------- /passwordmaker/libs/tasermonkeys-gjson-1.7.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/libs/tasermonkeys-gjson-1.7.1.jar -------------------------------------------------------------------------------- /passwordmaker/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /passwordmaker/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /opt/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -dontobfuscate 12 | 13 | # If your project uses WebView with JS, uncomment the following 14 | # and specify the fully qualified class name to the JavaScript interface 15 | # class: 16 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 17 | # public *; 18 | #} 19 | -dontwarn javax.naming.** 20 | -dontwarn javax.annotation.** 21 | -dontwarn javax.xml.stream.** 22 | -dontwarn sun.misc.Unsafe 23 | -keep class org.spongycastle.** -------------------------------------------------------------------------------- /passwordmaker/set_signing_env_vars.sh: -------------------------------------------------------------------------------- 1 | # this file must not be ran as a separate process in order to modify your environment. 2 | # intead use this file at the CLI by doing: source set_signing_env_vars.sh 3 | echo -n "Keystore file: " 4 | if [ -n "$PASSWORDMAKER_KEYSTORE_FILE" ]; then 5 | echo -n "($PASSWORDMAKER_KEYSTORE_FILE) " 6 | fi 7 | read PASSWORDMAKER_KEYSTORE_FILE_TMP 8 | if [ -n "$PASSWORDMAKER_KEYSTORE_FILE_TMP" ]; then 9 | PASSWORDMAKER_KEYSTORE_FILE=$PASSWORDMAKER_KEYSTORE_FILE_TMP 10 | unset PASSWORDMAKER_KEYSTORE_FILE_TMP 11 | fi 12 | echo "File set to: $PASSWORDMAKER_KEYSTORE_FILE" 13 | echo -n "Keystore password: " 14 | read -s PASSWORDMAKER_KEYSTORE_PASSWORD 15 | echo -n "\nKey alias: " 16 | 17 | if [ -n "$PASSWORDMAKER_KEYSTORE_KEY_ALIAS" ]; then 18 | echo -n "($PASSWORDMAKER_KEYSTORE_KEY_ALIAS) " 19 | fi 20 | read PASSWORDMAKER_KEYSTORE_KEY_ALIAS_TMP 21 | if [ -n "$PASSWORDMAKER_KEYSTORE_KEY_ALIAS_TMP" ]; then 22 | PASSWORDMAKER_KEYSTORE_KEY_ALIAS=$PASSWORDMAKER_KEYSTORE_KEY_ALIAS_TMP 23 | unset PASSWORDMAKER_KEYSTORE_KEY_ALIAS_TMP 24 | fi 25 | echo "Key alias set to: $PASSWORDMAKER_KEYSTORE_KEY_ALIAS" 26 | echo -n "Key password: " 27 | read -s PASSWORDMAKER_KEYSTORE_KEY_PASSWORD 28 | echo 29 | 30 | export PASSWORDMAKER_KEYSTORE_FILE 31 | export PASSWORDMAKER_KEYSTORE_PASSWORD 32 | export PASSWORDMAKER_KEYSTORE_KEY_ALIAS 33 | export PASSWORDMAKER_KEYSTORE_KEY_PASSWORD 34 | -------------------------------------------------------------------------------- /passwordmaker/src/androidTest/java/org/passwordmaker/PwmTest.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker; 2 | 3 | import org.daveware.passwordmaker.*; 4 | 5 | import java.security.Security; 6 | 7 | import static org.passwordmaker.TestUtils.saToString; 8 | 9 | /** 10 | * The purpose of this test is to make sure that the hash functions are available on the Android Emulator. 11 | * 12 | * The real unit tests for this functionality is in the passwordmaker-je-lib 13 | * 14 | */ 15 | public class PwmTest extends junit.framework.TestCase { 16 | static { 17 | PasswordMaker.setDefaultCryptoProvider("SC"); 18 | Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1); 19 | } 20 | 21 | protected void performTest(AlgorithmType algorithmType, boolean useHMac, String expected) throws Exception { 22 | Account profile = new Account(); 23 | profile.setCharacterSet(CharacterSets.ALPHANUMERIC); 24 | profile.setAlgorithm(algorithmType); 25 | profile.setHmac(useHMac); 26 | profile.setLength(8); 27 | profile.clearUrlComponents(); 28 | profile.addUrlComponent(Account.UrlComponents.Domain); 29 | 30 | SecureUTF8String masterPassword = new SecureUTF8String("happy"); 31 | 32 | PasswordMaker pwm = new PasswordMaker(); 33 | assertEquals(expected, saToString(pwm.makePassword(masterPassword, profile, "google.com"))); 34 | } 35 | 36 | public void testMD5() throws Exception { 37 | performTest(AlgorithmType.MD5, false, "HRdgNiyh"); 38 | } 39 | public void testMD4() throws Exception { 40 | performTest(AlgorithmType.MD4, false, "HtzLxeLD"); 41 | } 42 | public void testRIPEMD160() throws Exception { 43 | performTest(AlgorithmType.RIPEMD160, false, "joh9YCZc"); 44 | } 45 | public void testSHA1() throws Exception { 46 | performTest(AlgorithmType.SHA1, false, "iEXyQtf6"); 47 | } 48 | public void testSHA256() throws Exception { 49 | performTest(AlgorithmType.SHA256, false, "w8BStwWP"); 50 | } 51 | public void testMD5HMac() throws Exception { 52 | performTest(AlgorithmType.MD5, true, "BdVB2Ye3"); 53 | } 54 | public void testMD4HMac() throws Exception { 55 | performTest(AlgorithmType.MD4, true, "FYrXl6y9"); 56 | } 57 | public void testRIPEMD160HMac() throws Exception { 58 | performTest(AlgorithmType.RIPEMD160, true, "IeMWw25Q"); 59 | } 60 | public void testSHA1HMac() throws Exception { 61 | performTest(AlgorithmType.SHA1, true, "YqVH5OAk"); 62 | } 63 | public void testSHA256HMac() throws Exception { 64 | performTest(AlgorithmType.SHA256, true, "Qljpvcsf"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /passwordmaker/src/androidTest/java/org/passwordmaker/TestUtils.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker; 2 | 3 | import org.daveware.passwordmaker.SecureCharArray; 4 | 5 | public class TestUtils { 6 | public static String saToString(SecureCharArray arr) { 7 | return new String(arr.getData()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /passwordmaker/src/androidTest/resources/test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 13 | 19 | 25 | 26 | -------------------------------------------------------------------------------- /passwordmaker/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 31 | 32 | 36 | 39 | 40 | 44 | 47 | 48 | 52 | 55 | 56 | 60 | 63 | 64 | 68 | 71 | 72 | 76 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/AccountManagerSamples.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import org.daveware.passwordmaker.*; 5 | 6 | @SuppressWarnings({"EmptyMethod", "UnusedParameters"}) 7 | public class AccountManagerSamples { 8 | 9 | public static void addSamples(AccountManager accountManager) { 10 | 11 | } 12 | 13 | public static void addSamples2(AccountManager accountManager) { 14 | Database pwmProfiles = accountManager.getPwmProfiles(); 15 | try { 16 | Account folder = new Account("Personal", true); 17 | folder.getChildren().add(new AccountBuilder().setAlgorithm(AlgorithmType.SHA256) 18 | .setName("Reddit") 19 | .setDesc("Reddit page") 20 | .setUrl("http://reddit.com") 21 | .setUsername("testUser1") 22 | .setCharacterSet(CharacterSets.BASE_93_SET) 23 | .setLength(20) 24 | .setPrefix("$r3d") 25 | .setModifier("1") 26 | .addWildcardPattern("reddit", "*://*.reddit.com/*") 27 | .addWildcardPattern("redditdomain", "*://reddit.com/*").build()); 28 | folder.getChildren().add(new AccountBuilder().setAlgorithm(AlgorithmType.SHA256) 29 | .setName("facebook") 30 | .setDesc("facebook page") 31 | .setUrl("http://facebook.com") 32 | .setUsername("testUser2") 33 | .setCharacterSet(CharacterSets.ALPHANUMERIC) 34 | .setLength(23) 35 | .setSuffix("$f@d") 36 | .setModifier("2") 37 | .addWildcardPattern("facebook", "*://*.facebook.com/*") 38 | .addWildcardPattern("facebookdomain", "*://facebook.com/*").build()); 39 | pwmProfiles.addAccount(pwmProfiles.getRootAccount(), folder); 40 | folder = new Account("Work", true); 41 | folder.getChildren().add(new AccountBuilder().setAlgorithm(AlgorithmType.RIPEMD160) 42 | .setName("stackoverflow") 43 | .setDesc("dev qa") 44 | .setUrl("http://stackoverflow.com") 45 | .setUsername("devuser1") 46 | .setCharacterSet(CharacterSets.ALPHANUMERIC) 47 | .setLength(23) 48 | .setPrefix("%D3v") 49 | .setModifier("3") 50 | .addWildcardPattern("stackoverflow", "*://*.stackoverflow.com/*") 51 | .addWildcardPattern("stackoverflowdomain", "*://stackoverflow.com/*").build()); 52 | folder.getChildren().add(new AccountBuilder().setAlgorithm(AlgorithmType.SHA1) 53 | .setName("github.com") 54 | .setDesc("code repo") 55 | .setUrl("http://github.com") 56 | .setUsername("devuser2") 57 | .setCharacterSet(CharacterSets.HEX) 58 | .setLength(23) 59 | .setPrefix("%c0D3") 60 | .setModifier("4") 61 | .addWildcardPattern("github", "*://*.github.com/*") 62 | .addWildcardPattern("githubdomain", "*://github.com/*").build()); 63 | pwmProfiles.addAccount(pwmProfiles.getRootAccount(), folder); 64 | } catch (Exception e) { 65 | throw new RuntimeException(e); 66 | } 67 | 68 | accountManager.getFavoriteUrls().addAll(ImmutableList.builder() 69 | .add("http://reddit.com/") 70 | .add("http://realnews.com/") 71 | .add("http://google.com/") 72 | .add("http://goo.gl/") 73 | .add("http://markerisred.com/") 74 | .add("http://github.com/") 75 | .add("http://www.stackoverflow.com/") 76 | .add("https://www.facebook.com/") 77 | .add("https://gmail.com/") 78 | .build()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/AccountDetailActivity.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.app.ActionBar; 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.support.v4.app.NavUtils; 8 | import android.view.MenuItem; 9 | 10 | import java.util.ArrayList; 11 | 12 | 13 | /** 14 | * An activity representing a single Account detail screen. This 15 | * activity is only used on handset devices. On tablet-size devices, 16 | * item details are presented side-by-side with a list of items 17 | * in a {@link AccountListActivity}. 18 | *

19 | * This activity is mostly just a 'shell' activity containing nothing 20 | * more than a {@link AccountDetailFragment}. 21 | */ 22 | public class AccountDetailActivity extends Activity { 23 | @SuppressWarnings("UnusedDeclaration") 24 | private static String LOG_TAG = Logtags.ACCOUNT_DETAIL_ACTIVITY.getTag(); 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_account_detail); 30 | 31 | String accId = getIntent().getStringExtra(AccountDetailFragment.ARG_ITEM_ID); 32 | 33 | // Show the Up button in the action bar. 34 | setDisplayHomeAsUpEnabled(); 35 | 36 | // savedInstanceState is non-null when there is fragment state 37 | // saved from previous configurations of this activity 38 | // (e.g. when rotating the screen from portrait to landscape). 39 | // In this case, the fragment will automatically be re-added 40 | // to its container so we don't need to manually add it. 41 | // For more information, see the Fragments API guide at: 42 | // 43 | // http://developer.android.com/guide/components/fragments.html 44 | // 45 | if (savedInstanceState == null) { 46 | // Create the detail fragment and add it to the activity 47 | // using a fragment transaction. 48 | Bundle arguments = new Bundle(); 49 | arguments.putString(AccountDetailFragment.ARG_ITEM_ID, accId); 50 | AccountDetailFragment fragment = new AccountDetailFragment(); 51 | fragment.setArguments(arguments); 52 | getFragmentManager().beginTransaction() 53 | .add(R.id.account_detail_container, fragment) 54 | .commit(); 55 | } 56 | } 57 | 58 | @Override 59 | public boolean onOptionsItemSelected(MenuItem item) { 60 | int id = item.getItemId(); 61 | if (id == android.R.id.home) { 62 | // This ID represents the Home or Up button. In the case of this 63 | // activity, the Up button is shown. Use NavUtils to allow users 64 | // to navigate up one level in the application structure. For 65 | // more details, see the Navigation pattern on Android Design: 66 | // 67 | // http://developer.android.com/design/patterns/navigation.html#up-vs-back 68 | // 69 | Intent intent = new Intent(this, AccountListActivity.class); 70 | ArrayList accStack = getIntent().getStringArrayListExtra(AccountListFragment.STATE_ACCOUNT_STACK); 71 | if ( accStack != null && !accStack.isEmpty() ) 72 | intent.putStringArrayListExtra(AccountListFragment.STATE_ACCOUNT_STACK, accStack); 73 | NavUtils.navigateUpTo(this, intent); 74 | return true; 75 | } 76 | return super.onOptionsItemSelected(item); 77 | } 78 | 79 | private void setDisplayHomeAsUpEnabled() { 80 | // prevent the possible nullpointer if getActionBar returns null. 81 | ActionBar actionBar = getActionBar(); 82 | if ( actionBar != null ) actionBar.setDisplayHomeAsUpEnabled(true); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/AccountListActivity.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.DialogInterface; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.support.v4.app.NavUtils; 8 | import android.support.v7.app.ActionBar; 9 | import android.support.v7.app.AppCompatActivity; 10 | import android.support.v7.widget.Toolbar; 11 | import android.util.Log; 12 | import android.view.Menu; 13 | import android.view.MenuItem; 14 | import android.view.View; 15 | import android.view.WindowManager; 16 | import android.widget.EditText; 17 | import android.widget.Toast; 18 | 19 | import org.daveware.passwordmaker.Account; 20 | import org.jetbrains.annotations.NotNull; 21 | 22 | 23 | /** 24 | * An activity representing a list of Accounts. This activity 25 | * has different presentations for handset and tablet-size devices. On 26 | * handsets, the activity presents a list of items, which when touched, 27 | * lead to a {@link AccountDetailActivity} representing 28 | * item details. On tablets, the activity presents the list of items and 29 | * item details side-by-side using two vertical panes. 30 | *

31 | * The activity makes heavy use of fragments. The list of items is a 32 | * {@link AccountListFragment} and the item details 33 | * (if present) is a {@link AccountDetailFragment}. 34 | *

35 | * This activity also implements the required 36 | * {@link AccountListFragment.Callbacks} interface 37 | * to listen for item selections. 38 | */ 39 | public class AccountListActivity extends AppCompatActivity 40 | implements AccountListFragment.Callbacks { 41 | 42 | 43 | private static final String LOG_TAG = Logtags.ACCOUNT_LIST_ACTIVITY.getTag(); 44 | /** 45 | * Whether or not the activity is in two-pane mode, i.e. running on a tablet 46 | * device. 47 | */ 48 | private boolean mTwoPane; 49 | 50 | @Override 51 | protected void onCreate(Bundle savedInstanceState) { 52 | super.onCreate(savedInstanceState); 53 | setContentView(R.layout.activity_account_list); 54 | setSupportActionBar((Toolbar)findViewById(R.id.main_toolbar)); 55 | setDisplayHomeAsUpEnabled(); 56 | 57 | if (findViewById(R.id.account_detail_container) != null) { 58 | // The detail container view will be present only in the 59 | // large-screen layouts (res/values-large and 60 | // res/values-sw600dp). If this view is present, then the 61 | // activity should be in two-pane mode. 62 | mTwoPane = true; 63 | 64 | // In two-pane mode, list items should be given the 65 | // 'activated' state when touched. 66 | getAccountListFragment().setActivateOnItemClick(); 67 | } 68 | // TODO: If exposing deep links into your app, handle intents here. 69 | } 70 | 71 | protected AccountListFragment getAccountListFragment() { 72 | return ((AccountListFragment) getFragmentManager().findFragmentById(R.id.fragment_account_list)); 73 | } 74 | 75 | @Override 76 | public boolean onCreateOptionsMenu(Menu menu) { 77 | // Inflate the menu; this adds items to the action bar if it is present. 78 | getMenuInflater().inflate(R.menu.account_list, menu); 79 | return true; 80 | } 81 | 82 | @Override 83 | public boolean onOptionsItemSelected(MenuItem item) { 84 | int id = item.getItemId(); 85 | if (id == R.id.action_account_add) { 86 | addNewAccount(); 87 | return true; 88 | } else if ( id == R.id.action_account_folder_add ) { 89 | addNewFolder(); 90 | return true; 91 | } else if (id == android.R.id.home) { 92 | // This ID represents the Home or Up button. In the case of this 93 | // activity, the Up button is shown. Use NavUtils to allow users 94 | // to navigate up one level in the application structure. For 95 | // more details, see the Navigation pattern on Android Design: 96 | // 97 | // http://developer.android.com/design/patterns/navigation.html#up-vs-back 98 | // 99 | NavUtils.navigateUpFromSameTask(this); 100 | return true; 101 | } 102 | return super.onOptionsItemSelected(item); 103 | } 104 | 105 | private void addNewAccount(String accountName) { 106 | getAccountListFragment().createNewAccount(accountName); 107 | } 108 | 109 | private void addNewAccount() { 110 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 111 | final EditText editView = new EditText(this); 112 | editView.setLines(1); 113 | editView.setMinimumWidth(200); 114 | builder.setView(editView); 115 | builder.setPositiveButton(R.string.AddProfile, 116 | new DialogInterface.OnClickListener() { 117 | public void onClick(DialogInterface dialog, int which) { 118 | String newProfile = editView.getText().toString(); 119 | addNewAccount(newProfile); 120 | } 121 | }); 122 | builder.setNegativeButton(R.string.Cancel, null); 123 | final AlertDialog alert = builder.create(); 124 | editView.setOnFocusChangeListener(new View.OnFocusChangeListener() { 125 | public void onFocusChange(View v, boolean hasFocus) { 126 | if (hasFocus) { 127 | alert.getWindow() 128 | .setSoftInputMode( 129 | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); 130 | } 131 | 132 | } 133 | }); 134 | builder.setCancelable(true); 135 | alert.show(); 136 | } 137 | 138 | private void addNewFolder(String folderName) { 139 | getAccountListFragment().createNewFolder(folderName); 140 | } 141 | 142 | private void addNewFolder() { 143 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 144 | final EditText editView = new EditText(this); 145 | editView.setLines(1); 146 | editView.setMinimumWidth(200); 147 | builder.setView(editView); 148 | builder.setPositiveButton(R.string.AddProfile, 149 | new DialogInterface.OnClickListener() { 150 | public void onClick(DialogInterface dialog, int which) { 151 | String newFolder = editView.getText().toString(); 152 | addNewFolder(newFolder); 153 | } 154 | }); 155 | builder.setNegativeButton(R.string.Cancel, null); 156 | final AlertDialog alert = builder.create(); 157 | editView.setOnFocusChangeListener(new View.OnFocusChangeListener() { 158 | public void onFocusChange(View v, boolean hasFocus) { 159 | if (hasFocus) { 160 | alert.getWindow() 161 | .setSoftInputMode( 162 | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); 163 | } 164 | 165 | } 166 | }); 167 | builder.setCancelable(true); 168 | alert.show(); 169 | } 170 | 171 | /** 172 | * Callback method from {@link AccountListFragment.Callbacks} 173 | * indicating that the item with the given ID was selected. 174 | */ 175 | @Override 176 | public void onItemView(Account account) { 177 | if (mTwoPane) { 178 | // In two-pane mode, show the detail view in this activity by 179 | // adding or replacing the detail fragment using a 180 | // fragment transaction. 181 | Bundle arguments = new Bundle(); 182 | arguments.putString(AccountDetailFragment.ARG_ITEM_ID, account.getId()); 183 | AccountDetailFragment fragment = new AccountDetailFragment(); 184 | fragment.setArguments(arguments); 185 | getFragmentManager().beginTransaction() 186 | .replace(R.id.account_detail_container, fragment) 187 | .commit(); 188 | 189 | } else { 190 | // In single-pane mode, simply start the detail activity 191 | // for the selected item ID. 192 | Intent detailIntent = new Intent(this, AccountDetailActivity.class); 193 | detailIntent.putExtra(AccountDetailFragment.ARG_ITEM_ID, account.getId()); 194 | getAccountListFragment().saveAccountListState(detailIntent); 195 | startActivity(detailIntent); 196 | } 197 | } 198 | 199 | @Override 200 | public void onFolderSelected(Account account) { 201 | // do anything that we need to do here 202 | } 203 | 204 | @Override 205 | public void onItemManuallySelected(Account account) { 206 | PwmApplication.getInstance().getAccountManager().selectAccountById(account.getId()); 207 | NavUtils.navigateUpFromSameTask(this); 208 | Toast.makeText(this, "Manually selected '" + account.getName() + "'", Toast.LENGTH_SHORT).show(); 209 | } 210 | 211 | private void setDisplayHomeAsUpEnabled() { 212 | ActionBar actionBar = getSupportActionBar(); 213 | if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true); 214 | } 215 | 216 | @Override 217 | protected void onSaveInstanceState(@NotNull Bundle outState) { 218 | Log.i(LOG_TAG, "onSaveInstanceState"); 219 | super.onSaveInstanceState(outState); 220 | } 221 | 222 | @Override 223 | protected void onRestoreInstanceState(@NotNull Bundle savedInstanceState) { 224 | Log.i(LOG_TAG, "onRestoreInstanceState"); 225 | super.onRestoreInstanceState(savedInstanceState); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/AccountListFragment.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.app.Activity; 4 | import android.app.ListFragment; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.support.v7.view.ActionMode; 9 | import android.view.Menu; 10 | import android.view.MenuInflater; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.widget.ArrayAdapter; 14 | import android.widget.ListView; 15 | import com.google.common.base.Function; 16 | import com.google.common.base.Predicate; 17 | import com.google.common.collect.Collections2; 18 | import com.google.common.collect.Lists; 19 | import org.daveware.passwordmaker.Account; 20 | import org.daveware.passwordmaker.AccountManager; 21 | import org.jetbrains.annotations.NotNull; 22 | import org.jetbrains.annotations.Nullable; 23 | 24 | import java.util.ArrayList; 25 | import java.util.Collection; 26 | import java.util.LinkedList; 27 | import java.util.List; 28 | 29 | /** 30 | * A list fragment representing a list of Accounts. This fragment 31 | * also supports tablet devices by allowing list items to be given an 32 | * 'activated' state upon selection. This helps indicate which item is 33 | * currently being viewed in a {@link AccountDetailFragment}. 34 | *

35 | * Activities containing this fragment MUST implement the {@link Callbacks} 36 | * interface. 37 | */ 38 | public class AccountListFragment extends ListFragment { 39 | 40 | /** 41 | * The serialization (saved instance state) Bundle key representing the 42 | * activated item position. Only used on tablets. 43 | */ 44 | private static final String STATE_ACTIVATED_POSITION = "activated_position"; 45 | public static final String STATE_ACCOUNT_STACK = "activated_account_stack"; 46 | @SuppressWarnings("UnusedDeclaration") 47 | private static final String LOG_TAG = Logtags.ACCOUNT_LIST_FRAGMENT.getTag(); 48 | /** 49 | * The fragment's current callback object, which is notified of list item 50 | * clicks. 51 | */ 52 | private Callbacks mCallbacks = sDummyCallbacks; 53 | 54 | private ActionMode mActionMode; 55 | private Menu mActionMenu; 56 | 57 | private boolean autoActivateMode = false; 58 | 59 | /** 60 | * The current activated item position. Only used on tablets. 61 | */ 62 | private int mActivatedPosition = ListView.INVALID_POSITION; 63 | private AccountManager accountManager; 64 | private Account loadedAccount; 65 | 66 | /** 67 | * A callback interface that all activities containing this fragment must 68 | * implement. This mechanism allows activities to be notified of item 69 | * selections. 70 | */ 71 | @SuppressWarnings("EmptyMethod") 72 | public interface Callbacks { 73 | /** 74 | * Callback for when an item has been selected. 75 | */ 76 | public void onItemView(Account account); 77 | public void onFolderSelected(Account account); 78 | public void onItemManuallySelected(Account account); 79 | } 80 | 81 | /** 82 | * A dummy implementation of the {@link Callbacks} interface that does 83 | * nothing. Used only when this fragment is not attached to an activity. 84 | */ 85 | private final static Callbacks sDummyCallbacks = new Callbacks() { 86 | @Override 87 | public void onItemView(Account account) { 88 | } 89 | 90 | @Override 91 | public void onFolderSelected(Account account) { 92 | 93 | } 94 | 95 | @Override 96 | public void onItemManuallySelected(Account account) { 97 | 98 | } 99 | }; 100 | 101 | private final AccountStack accountStack = new AccountStack(); 102 | 103 | 104 | 105 | /** 106 | * Mandatory empty constructor for the fragment manager to instantiate the 107 | * fragment (e.g. upon screen orientation changes). 108 | */ 109 | public AccountListFragment() { 110 | } 111 | 112 | @Override 113 | public void onCreate(Bundle savedInstanceState) { 114 | super.onCreate(savedInstanceState); 115 | accountManager = PwmApplication.getInstance().getAccountManager(); 116 | setListAdapter(new ArrayAdapter( 117 | getActivity(), 118 | android.R.layout.simple_list_item_activated_1, 119 | android.R.id.text1)); 120 | loadIncomingAccount(); 121 | } 122 | 123 | @Override 124 | public void onViewCreated(View view, Bundle savedInstanceState) { 125 | super.onViewCreated(view, savedInstanceState); 126 | // Restore the previously serialized activated item position. 127 | if (savedInstanceState != null 128 | && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) { 129 | setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION)); 130 | } 131 | } 132 | 133 | private void loadIncomingAccount() { 134 | Intent intent = getActivity().getIntent(); 135 | String accountId = intent.getStringExtra(AccountDetailFragment.ARG_ITEM_ID); 136 | ArrayList accStack = intent.getStringArrayListExtra(AccountListFragment.STATE_ACCOUNT_STACK); 137 | if ( accStack == null || accStack.isEmpty()) { 138 | accountStack.clearToRoot(); 139 | if ( accountId != null && !accountId.isEmpty() ) { 140 | List pathToAccount = accountManager.getPwmProfiles().findPathToAccountById(accountId); 141 | // this is saved so that we can notify the main list that we should display the details of the selected 142 | // account right when they set the callback 143 | loadedAccount = pathToAccount.remove(pathToAccount.size() - 1); 144 | accountStack.replace(pathToAccount); 145 | 146 | } 147 | } else { 148 | accountStack.loadFromIds(accStack); 149 | } 150 | refreshList(accountStack.getCurrentAccount()); 151 | } 152 | 153 | @Override 154 | public void onAttach(Activity activity) { 155 | super.onAttach(activity); 156 | 157 | // Activities containing this fragment must implement its callbacks. 158 | if (!(activity instanceof Callbacks)) { 159 | throw new IllegalStateException("Activity must implement fragment's callbacks."); 160 | } 161 | 162 | mCallbacks = (Callbacks) activity; 163 | if ( loadedAccount != null ) { 164 | Account acc = loadedAccount; 165 | loadedAccount = null; 166 | mCallbacks.onItemView(acc); 167 | } 168 | 169 | } 170 | 171 | @Override 172 | public void onDetach() { 173 | super.onDetach(); 174 | // Reset the active callbacks interface to the dummy implementation. 175 | mCallbacks = sDummyCallbacks; 176 | } 177 | 178 | @Override 179 | public void onListItemClick(ListView listView, View view, int position, long id) { 180 | super.onListItemClick(listView, view, position, id); 181 | 182 | // Notify the active callbacks interface (the activity, if the 183 | // fragment is attached to one) that an item has been selected. 184 | Account selected = getCurrentAccountList().getItem(position); 185 | if ( selected.hasChildren() ) { 186 | goIntoFolder(selected); 187 | } else { 188 | if (mActionMode != null ) { 189 | if (mActionMenu != null) { 190 | if (getCheckedAccount().isDefault()) { 191 | mActionMenu.findItem(R.id.menu_item_delete).setVisible(false); 192 | } else { 193 | mActionMenu.findItem(R.id.menu_item_delete).setVisible(true); 194 | } 195 | } 196 | return; 197 | } 198 | if ( ! autoActivateMode ) { 199 | getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); 200 | } 201 | getListView().setItemChecked(position, true); 202 | 203 | // Start the CAB using the ActionMode.Callback defined above 204 | mActionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(mActionModeCallback); 205 | } 206 | } 207 | 208 | protected void goIntoFolder(Account folder) { 209 | accountStack.pushCurrentAccount(folder); 210 | refreshList(folder); 211 | mCallbacks.onFolderSelected(folder); 212 | } 213 | 214 | @Override 215 | public void onSaveInstanceState(Bundle outState) { 216 | super.onSaveInstanceState(outState); 217 | if (mActivatedPosition != ListView.INVALID_POSITION) { 218 | // Serialize and persist the activated item position. 219 | outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition); 220 | } 221 | outState.putStringArrayList(STATE_ACCOUNT_STACK, accountStack.getIds()); 222 | } 223 | 224 | public void saveAccountListState(Intent intent) { 225 | intent.putStringArrayListExtra(AccountListFragment.STATE_ACCOUNT_STACK, accountStack.getIds()); 226 | } 227 | 228 | /** 229 | * Turns on activate-on-click mode. When this mode is on, list items will be 230 | * given the 'activated' state when touched. 231 | */ 232 | public void setActivateOnItemClick() { 233 | // When setting CHOICE_MODE_SINGLE, ListView will automatically 234 | // give items the 'activated' state when touched. 235 | getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); 236 | autoActivateMode = true; 237 | } 238 | 239 | private void setActivatedPosition(int position) { 240 | if (position == ListView.INVALID_POSITION) { 241 | getListView().setItemChecked(mActivatedPosition, false); 242 | } else { 243 | getListView().setItemChecked(position, true); 244 | } 245 | 246 | mActivatedPosition = position; 247 | } 248 | 249 | @SuppressWarnings("unchecked") 250 | private ArrayAdapter getCurrentAccountList() { 251 | return (ArrayAdapter) getListAdapter(); 252 | } 253 | 254 | private void refreshList(@NotNull Account account) { 255 | ArrayAdapter accounts = getCurrentAccountList(); 256 | accounts.setNotifyOnChange(false); 257 | accounts.clear(); 258 | accounts.addAll(account.getChildren()); 259 | accounts.notifyDataSetChanged(); 260 | } 261 | 262 | public void createNewAccount(String accountName) { 263 | try { 264 | Account account = new Account(accountName, false); 265 | account.copySettings(accountManager.getDefaultAccount()); 266 | account.setName(accountName); 267 | account.setIsFolder(false); 268 | account.clearUrlComponents(); 269 | account.addUrlComponent(Account.UrlComponents.Domain); 270 | account.getPatterns().clear(); 271 | accountManager.getPwmProfiles().addAccount(accountStack.getCurrentAccount(), account); 272 | getCurrentAccountList().notifyDataSetChanged(); 273 | mCallbacks.onItemView(account); 274 | } catch (Exception e) { 275 | throw new RuntimeException(e); 276 | } 277 | } 278 | 279 | protected void deleteAccount(Account account) { 280 | accountManager.getPwmProfiles().removeAccount(account); 281 | refreshList(accountStack.getCurrentAccount()); 282 | getCurrentAccountList().notifyDataSetChanged(); 283 | getListView().clearChoices(); 284 | } 285 | 286 | 287 | public void createNewFolder(String folderName) { 288 | try { 289 | Account account = new Account(folderName, true); 290 | accountManager.getPwmProfiles().addAccount(accountStack.getCurrentAccount(), account); 291 | getCurrentAccountList().notifyDataSetChanged(); 292 | goIntoFolder(account); 293 | } catch (Exception e) { 294 | throw new RuntimeException(e); 295 | } 296 | } 297 | 298 | private void clearAllChecked() { 299 | final ListView lv = getListView(); 300 | getListView().clearChoices(); 301 | for (int i = 0; i < lv.getCount(); i++) 302 | lv.setItemChecked(i, false); 303 | if ( ! autoActivateMode ) { 304 | lv.post(new Runnable() { 305 | @Override 306 | public void run() { 307 | lv.setChoiceMode(ListView.CHOICE_MODE_NONE); 308 | } 309 | }); 310 | 311 | } 312 | else if ( mActivatedPosition != ListView.INVALID_POSITION ) { 313 | setActivatedPosition(mActivatedPosition); 314 | } 315 | getCurrentAccountList().notifyDataSetChanged(); 316 | } 317 | 318 | private class AccountStack { 319 | private final LinkedList accountStack = new LinkedList(); 320 | 321 | public int size() { 322 | return accountStack.size(); 323 | } 324 | 325 | public void clearToRoot() { 326 | pushCurrentAccount(null); 327 | } 328 | 329 | public void replace(Collection accounts) { 330 | clearToRoot(); 331 | // can't use add all since it will be in reverse order 332 | for ( Account a : accounts ) accountStack.push(a); 333 | } 334 | 335 | // I think this might belong in the fragment 336 | public void pushCurrentAccount(@Nullable Account account) { 337 | if ( account == null ) { 338 | accountStack.clear(); 339 | account = accountManager.getPwmProfiles().getRootAccount(); 340 | } 341 | if ( accountStack.peek() != account ) 342 | accountStack.push(account); 343 | } 344 | 345 | @SuppressWarnings("UnusedDeclaration") 346 | public Account popAccount() { 347 | // always ensure there is at least the root on the stack 348 | if ( accountStack.isEmpty() ) 349 | accountStack.push(accountManager.getPwmProfiles().getRootAccount()); 350 | return accountStack.peek(); 351 | } 352 | 353 | @NotNull 354 | public Account getCurrentAccount() { 355 | if ( accountStack.isEmpty() ) { 356 | accountStack.push(accountManager.getPwmProfiles().getRootAccount()); 357 | } 358 | return accountStack.peek(); 359 | } 360 | 361 | public ArrayList getIds() { 362 | Collection accountStackIds = Lists.transform(accountStack, new Function() { 363 | @Override 364 | public String apply(Account account) { 365 | return account.getId(); 366 | } 367 | }); 368 | return new ArrayList(accountStackIds); 369 | } 370 | 371 | private void loadFromIds(List accountIdStack) { 372 | if ( accountIdStack == null ) return; 373 | accountStack.clear(); 374 | Collection accounts = Collections2.filter(Lists.transform(accountIdStack, 375 | new Function() { 376 | @Override 377 | public Account apply(String id) { 378 | return accountManager.getPwmProfiles().findAccountById(id); 379 | } 380 | }), new Predicate() { 381 | @Override 382 | public boolean apply(Account account) { 383 | return account != null; 384 | } 385 | }); 386 | accountStack.addAll(accounts); 387 | } 388 | 389 | } 390 | 391 | 392 | private final ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { 393 | 394 | // Called when the action mode is created; startActionMode() was called 395 | @Override 396 | public boolean onCreateActionMode(ActionMode mode, Menu menu) { 397 | // Inflate a menu resource providing context menu items 398 | MenuInflater inflater = mode.getMenuInflater(); 399 | inflater.inflate(R.menu.account_list_menu, menu); 400 | if (getCheckedAccount().isDefault()) { 401 | menu.findItem(R.id.menu_item_delete).setVisible(false); 402 | } else { 403 | menu.findItem(R.id.menu_item_delete).setVisible(true); 404 | } 405 | mActionMenu = menu; 406 | return true; 407 | } 408 | 409 | // Called each time the action mode is shown. Always called after onCreateActionMode, but 410 | // may be called multiple times if the mode is invalidated. 411 | @Override 412 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 413 | return false; // Return false if nothing is done 414 | } 415 | 416 | // Called when the user selects a contextual menu item 417 | @Override 418 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 419 | switch (item.getItemId()) { 420 | case R.id.menu_item_select: 421 | mCallbacks.onItemManuallySelected(getCheckedAccount()); 422 | mode.finish(); // Action picked, so close the CAB 423 | return true; 424 | case R.id.menu_item_delete: 425 | deleteAccount(getCheckedAccount()); 426 | mode.finish(); 427 | return true; 428 | case R.id.menu_item_view: 429 | mCallbacks.onItemView(getCheckedAccount()); 430 | mode.finish(); 431 | return true; 432 | default: 433 | return false; 434 | } 435 | } 436 | 437 | // Called when the user exits the action mode 438 | @Override 439 | public void onDestroyActionMode(ActionMode mode) { 440 | mActionMode = null; 441 | mActionMenu = null; 442 | clearAllChecked(); 443 | } 444 | }; 445 | 446 | 447 | 448 | public Account getCheckedAccount() { 449 | return getCurrentAccountList().getItem(getListView().getCheckedItemPosition()); 450 | } 451 | } 452 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/AlgorithmSelectionValues.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import org.daveware.passwordmaker.Account; 4 | import org.daveware.passwordmaker.AlgorithmType; 5 | 6 | import java.util.NoSuchElementException; 7 | 8 | @SuppressWarnings("UnusedDeclaration") 9 | public enum AlgorithmSelectionValues { 10 | // The stringResourcePosition must match that of the strings.xml HashAlgos array index 11 | 12 | MD4(AlgorithmType.MD4, false, true, 0), 13 | HMAC_MD4(AlgorithmType.MD4, true, true, 1), 14 | MD5(AlgorithmType.MD5, false, true, 2), 15 | MD5_06(AlgorithmType.MD5, false, false, 3), 16 | HMAC_MD5(AlgorithmType.MD5, true, true, 4), 17 | HMAC_MD5_06(AlgorithmType.MD5, true, false, 5), 18 | SHA1(AlgorithmType.SHA1, false, true, 6), 19 | HMAC_SHA1(AlgorithmType.SHA1, true, true, 7), 20 | SHA256(AlgorithmType.SHA256, false, true, 8), 21 | HMAC_SHA256(AlgorithmType.SHA256, true, true, 9), 22 | RIPEMD160(AlgorithmType.RIPEMD160, false, true, 10), 23 | HMAC_RIPEMD160(AlgorithmType.RIPEMD160, true, true, 11) 24 | ; 25 | 26 | 27 | private final AlgorithmType aglo; 28 | private final boolean isHMac; 29 | private final boolean isTrimmed; 30 | private final int stringResourcePosition; 31 | 32 | AlgorithmSelectionValues(AlgorithmType aglo, boolean isHMac, boolean isTrimmed, int stringResourcePosition) { 33 | this.aglo = aglo; 34 | this.isHMac = isHMac; 35 | this.isTrimmed = isTrimmed; 36 | this.stringResourcePosition = stringResourcePosition; 37 | } 38 | 39 | public AlgorithmType getAlgo() { 40 | return aglo; 41 | } 42 | 43 | public boolean isHMac() { 44 | return isHMac; 45 | } 46 | 47 | public boolean isTrimmed() { 48 | return isTrimmed; 49 | } 50 | 51 | public int getStringResourcePosition() { 52 | return stringResourcePosition; 53 | } 54 | 55 | public static AlgorithmSelectionValues getByPosition(int index) { 56 | for (AlgorithmSelectionValues v : values()) { 57 | if ( v.stringResourcePosition == index ) return v; 58 | } 59 | throw new NoSuchElementException("Invalid index: " + index); 60 | } 61 | 62 | public static AlgorithmSelectionValues getFromAccount(Account account) { 63 | for (AlgorithmSelectionValues v : values()) { 64 | if ( v.isHMac == account.isHmac() && 65 | v.isTrimmed == account.isTrim() && 66 | v.getAlgo() == account.getAlgorithm() ) return v; 67 | } 68 | throw new NoSuchElementException("Could not find algorithm selection combination from account '" 69 | + account.getId() + "(" + account.getName() + ")' setup"); 70 | } 71 | 72 | public void setAccountSettings(Account account) { 73 | account.setHmac(isHMac); 74 | account.setTrim(isTrimmed); 75 | account.setAlgorithm(aglo); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/AndroidGlobalSettings.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import org.daveware.passwordmaker.GlobalSettingKey; 4 | 5 | public class AndroidGlobalSettings { 6 | public final static GlobalSettingKey FAVORITES = new GlobalSettingKey("NS1:favorites", ""); 7 | public final static GlobalSettingKey MASTER_PASSWORD_HASH = new GlobalSettingKey("NS1:MASTER_PWD_HASH", ""); 8 | public final static GlobalSettingKey STORE_MASTER_PASSWORD_HASH = new GlobalSettingKey("NS1:STORE_MASTER_PWD_HASH", "false"); 9 | public final static GlobalSettingKey MASTER_PASSWORD_SALT = new GlobalSettingKey("NS1:MASTER_PWD_SALT", ""); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/AndroidRDFDatabaseWriter.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import org.daveware.passwordmaker.RDFDatabaseWriter; 4 | import org.daveware.passwordmaker.xmlwrappers.XmlIOException; 5 | import org.daveware.passwordmaker.xmlwrappers.XmlStreamWriter; 6 | import org.passwordmaker.android.xmlwrappers.AndroidXmlStreamWriter; 7 | 8 | import java.io.Writer; 9 | 10 | /** 11 | * This is a specialized version of the default RDFDatabaseWriter that just changes the underlining XMLStreamWriter 12 | * to one that exist in the Android ecosystem. 13 | */ 14 | public class AndroidRDFDatabaseWriter extends RDFDatabaseWriter { 15 | @Override 16 | protected XmlStreamWriter newXmlStreamWriter(Writer writer) throws XmlIOException { 17 | return new AndroidXmlStreamWriter(writer); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/ClassicSettingsImporter.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | import com.google.common.collect.Lists; 6 | import com.google.common.collect.Sets; 7 | import com.tasermonkeys.google.json.*; 8 | import com.tasermonkeys.google.json.reflect.TypeToken; 9 | import org.daveware.passwordmaker.*; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import java.io.*; 14 | import java.util.*; 15 | 16 | import static com.google.common.io.Closeables.closeQuietly; 17 | 18 | public class ClassicSettingsImporter { 19 | private final Database database = new Database(); 20 | private final Set favorites = Sets.newHashSet(); 21 | private final Gson gson = new Gson(); 22 | private static final String REPO_PROFILES_FILENAME = "profiles.pss"; 23 | private static final String UPGRADED_MARKER = "profiles.upgrademarker"; 24 | private static final String LOG_TAG = Logtags.CLASSIC_SETTINGS_IMPORTER.getTag(); 25 | 26 | 27 | private ClassicSettingsImporter(Reader reader) throws IOException { 28 | readFromReader(reader); 29 | } 30 | 31 | private void readFromReader(Reader reader) throws IOException { 32 | JsonParser parser = new JsonParser(); 33 | JsonElement element = parser.parse(reader); 34 | JsonObject obj = element.getAsJsonObject(); 35 | parseAccountList(database.getRootAccount(), obj); 36 | } 37 | 38 | protected Database toDatabase() { 39 | return database; 40 | } 41 | 42 | protected Collection getFavorites() { 43 | return favorites; 44 | } 45 | 46 | /** 47 | * This function will only upgrade once. After the first time ran, its a no-op. 48 | * Side effect is a new file on the filesystem will be created named {@value #UPGRADED_MARKER} 49 | * @param context - The context to which base the filesystem calls on 50 | * @param favoritesOut - it clears it out, then add all favorites from all of the extracted profiles 51 | * 52 | * @return a created database if successful. Null otherwise. 53 | * @throws IOException - On error importing the database. 54 | */ 55 | @Nullable 56 | public static Database importIntoDatabase(@NotNull Context context, @NotNull Collection favoritesOut) throws IOException { 57 | File f = new File(context.getFilesDir(), REPO_PROFILES_FILENAME); 58 | File upgradedMarker = new File(context.getFilesDir(), UPGRADED_MARKER); 59 | // The upgrade marker is so that we can only upgrade once, but at the same time allow for the possibility of a 60 | // downgrade of software version if a bug happens. 61 | if (!f.exists() || upgradedMarker.exists() ) 62 | return null; 63 | Log.i(LOG_TAG, "Upgrading classic database to new one"); 64 | InputStream fis = context.openFileInput(REPO_PROFILES_FILENAME); 65 | Database db = null; 66 | try { 67 | Reader reader = new InputStreamReader(fis, "UTF-8"); 68 | ClassicSettingsImporter importer = new ClassicSettingsImporter(reader); 69 | db = importer.toDatabase(); 70 | favoritesOut.clear(); 71 | favoritesOut.addAll(importer.getFavorites()); 72 | } finally { 73 | closeQuietly(fis); 74 | } 75 | // only will get here if we didn't throw before 76 | FileOutputStream touchFile = null; 77 | try { 78 | touchFile = new FileOutputStream(upgradedMarker); 79 | touchFile.write(new Date().toString().getBytes()); 80 | } catch (IOException ignored) { 81 | } finally { 82 | try { 83 | if ( touchFile != null ) touchFile.close(); 84 | } catch (IOException ignore) {} 85 | } 86 | return db; 87 | } 88 | 89 | protected void parseAccountList(Account parent, JsonObject accounts) throws IOException { 90 | for (Map.Entry x : accounts.entrySet()) { 91 | try { 92 | database.addAccount(parent, parseAccount((JsonObject)x.getValue()) ); 93 | } catch (Exception e) { 94 | throw new IOException(e); 95 | } 96 | } 97 | } 98 | 99 | private Account parseAccount(JsonObject jsonAccount) { 100 | Account account = new Account(jsonAccount.get("name").getAsString(), "", jsonAccount.get("username").getAsString()); 101 | account.setId(Account.createId()); 102 | account.setCharacterSet(jsonAccount.get("characters").getAsString()); 103 | OldHashAlgo hashAlgo = OldHashAlgo.valueOf(jsonAccount.get("currentAlgo").getAsString()); 104 | account.setAlgorithm(hashAlgo.algo.getAlgo()); 105 | account.setHmac(hashAlgo.algo.isHMac()); 106 | account.setTrim(hashAlgo.algo.isTrimmed()); 107 | OldLeetLevel leetLevel =OldLeetLevel.valueOf(jsonAccount.get("leetLevel").getAsString()); 108 | account.setLeetLevel(leetLevel.leetLevel); 109 | OldUseLeet useLeet = OldUseLeet.valueOf(jsonAccount.get("useLeet").getAsString()); 110 | account.setLeetType(useLeet.leetType); 111 | account.setModifier(jsonAccount.get("modifier").getAsString()); 112 | account.setPrefix(jsonAccount.get("passwordPrefix").getAsString()); 113 | account.setSuffix(jsonAccount.get("passwordSuffix").getAsString()); 114 | Set esUrls = parseUrlParts(jsonAccount.get("urlComponents")); 115 | account.setUrlComponents(esUrls); 116 | account.setLength(jsonAccount.get("lengthOfPassword").getAsShort()); 117 | List accountFavs = asStringArray(jsonAccount.getAsJsonArray("pwmFavoriteInputs")); 118 | // this will allow these accounts to be auto-selected for the favorites 119 | for (String fav : accountFavs ) { 120 | AccountPatternData accPtnData = new AccountPatternData(); 121 | accPtnData.setDesc(fav + ": Imported from classic"); 122 | accPtnData.setEnabled(true); 123 | accPtnData.setPattern(fav); 124 | accPtnData.setType(AccountPatternType.WILDCARD); 125 | account.getPatterns().add(accPtnData); 126 | } 127 | favorites.addAll(accountFavs); 128 | 129 | return account; 130 | } 131 | 132 | private List asStringArray(JsonArray array) { 133 | List errs = Lists.newArrayList(); 134 | List result = Lists.newArrayListWithCapacity(array.size()); 135 | for (int i = 0; i < array.size(); ++i) { 136 | try { 137 | result.add(array.get(i).getAsString()); 138 | } catch (Exception e) { 139 | errs.add(e.getMessage()); 140 | } 141 | } 142 | if ( ! errs.isEmpty() ) { 143 | Log.e(LOG_TAG, "Error while reading json array: " + errs.toString(), new IOException(errs.get(0))); 144 | } 145 | return result; 146 | } 147 | 148 | private Set parseUrlParts(JsonElement urlSetElement) { 149 | List urlComponents = gson.fromJson( urlSetElement, new TypeToken>() {}.getType()); 150 | Set esUrls = EnumSet.noneOf(Account.UrlComponents.class); 151 | for (String urlComp : urlComponents) { 152 | esUrls.add(OldUrlComponents.valueOf(urlComp).urlComponent); 153 | } 154 | return esUrls; 155 | } 156 | 157 | private enum OldHashAlgo { 158 | MD4(AlgorithmSelectionValues.MD4), 159 | HMAC_MD4(AlgorithmSelectionValues.HMAC_MD4), 160 | MD5(AlgorithmSelectionValues.MD5), 161 | MD5_Version_0_6(AlgorithmSelectionValues.MD5_06), 162 | HMAC_MD5(AlgorithmSelectionValues.HMAC_MD5), 163 | HMAC_MD5_Version_0_6(AlgorithmSelectionValues.HMAC_MD5_06), 164 | SHA_1(AlgorithmSelectionValues.SHA1), 165 | HMAC_SHA_1(AlgorithmSelectionValues.HMAC_SHA1), 166 | SHA_256(AlgorithmSelectionValues.HMAC_SHA256), 167 | HMAC_SHA_256(AlgorithmSelectionValues.HMAC_SHA256), 168 | HMAC_SHA_256_Version_1_5_1(AlgorithmSelectionValues.HMAC_SHA256), 169 | RIPEMD_160(AlgorithmSelectionValues.RIPEMD160), 170 | HMAC_RIPEMD_160(AlgorithmSelectionValues.HMAC_RIPEMD160); 171 | 172 | final AlgorithmSelectionValues algo; 173 | 174 | OldHashAlgo(AlgorithmSelectionValues algo) { 175 | this.algo = algo; 176 | } 177 | } 178 | 179 | private enum OldLeetLevel { 180 | One(LeetLevel.LEVEL1), 181 | Two(LeetLevel.LEVEL2), 182 | Three(LeetLevel.LEVEL3), 183 | Four(LeetLevel.LEVEL4), 184 | Five(LeetLevel.LEVEL5), 185 | Six(LeetLevel.LEVEL6), 186 | Seven(LeetLevel.LEVEL7), 187 | Eight(LeetLevel.LEVEL8), 188 | Nine(LeetLevel.LEVEL9); 189 | 190 | public final LeetLevel leetLevel; 191 | 192 | OldLeetLevel(LeetLevel leetLevel) { 193 | this.leetLevel = leetLevel; 194 | } 195 | } 196 | 197 | private enum OldUseLeet { 198 | NotAtAll(LeetType.NONE), 199 | BeforeGeneratingPassword(LeetType.BEFORE), 200 | AfterGeneratingPassword(LeetType.AFTER), 201 | BeforeAndAfterGeneratingPassword(LeetType.BOTH); 202 | 203 | public final LeetType leetType; 204 | 205 | OldUseLeet(LeetType leetType) { 206 | this.leetType = leetType; 207 | } 208 | } 209 | 210 | private enum OldUrlComponents { 211 | Protocol(Account.UrlComponents.Protocol), 212 | Subdomain(Account.UrlComponents.Subdomain), 213 | Domain(Account.UrlComponents.Domain), 214 | PortPathAnchorQuery(Account.UrlComponents.PortPathAnchorQuery); 215 | 216 | public final Account.UrlComponents urlComponent; 217 | 218 | 219 | OldUrlComponents(Account.UrlComponents urlComponent) { 220 | this.urlComponent = urlComponent; 221 | } 222 | 223 | } 224 | 225 | 226 | } 227 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/EditFavoritesActivity.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.DialogInterface; 5 | import android.os.Bundle; 6 | import android.support.v4.app.NavUtils; 7 | import android.support.v7.app.ActionBar; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.Toolbar; 10 | import android.view.Menu; 11 | import android.view.MenuItem; 12 | import android.view.View; 13 | import android.view.WindowManager; 14 | import android.widget.EditText; 15 | 16 | 17 | public class EditFavoritesActivity extends AppCompatActivity { 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_edit_favorites); 23 | setSupportActionBar((Toolbar)findViewById(R.id.main_toolbar)); 24 | // Show the Up button in the action bar. 25 | setDisplayHomeAsUpEnabled(); 26 | } 27 | 28 | protected EditFavoritesFragment getAccountListFragment() { 29 | return ((EditFavoritesFragment) getFragmentManager().findFragmentById(R.id.edit_favorites)); 30 | } 31 | 32 | 33 | @Override 34 | public boolean onCreateOptionsMenu(Menu menu) { 35 | // Inflate the menu; this adds items to the action bar if it is present. 36 | getMenuInflater().inflate(R.menu.edit_favorites, menu); 37 | return true; 38 | } 39 | 40 | @Override 41 | public boolean onOptionsItemSelected(MenuItem item) { 42 | // Handle action bar item clicks here. The action bar will 43 | // automatically handle clicks on the Home/Up button, so long 44 | // as you specify a parent activity in AndroidManifest.xml. 45 | int id = item.getItemId(); 46 | if (id == R.id.action_favs_add) { 47 | newFavorite(); 48 | return true; 49 | } else if ( id == android.R.id.home) { 50 | // This ID represents the Home or Up button. In the case of this 51 | // activity, the Up button is shown. Use NavUtils to allow users 52 | // to navigate up one level in the application structure. For 53 | // more details, see the Navigation pattern on Android Design: 54 | // 55 | // http://developer.android.com/design/patterns/navigation.html#up-vs-back 56 | // 57 | NavUtils.navigateUpFromSameTask(this); 58 | return true; 59 | } 60 | return super.onOptionsItemSelected(item); 61 | } 62 | 63 | protected void addItem(String title) { 64 | getAccountListFragment().addItem(title); 65 | 66 | } 67 | 68 | private void newFavorite() { 69 | AlertDialog.Builder builder = new AlertDialog.Builder(this); 70 | final EditText editView = new EditText(this); 71 | editView.setLines(1); 72 | editView.setMinimumWidth(200); 73 | builder.setView(editView); 74 | builder.setPositiveButton(R.string.AddFavorite, 75 | new DialogInterface.OnClickListener() { 76 | public void onClick(DialogInterface dialog, int which) { 77 | String newFav = editView.getText().toString(); 78 | addItem(newFav); 79 | } 80 | }); 81 | builder.setNegativeButton(R.string.Cancel, null); 82 | final AlertDialog alert = builder.create(); 83 | editView.setOnFocusChangeListener(new View.OnFocusChangeListener() { 84 | public void onFocusChange(View v, boolean hasFocus) { 85 | if (hasFocus) { 86 | alert.getWindow() 87 | .setSoftInputMode( 88 | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); 89 | } 90 | 91 | } 92 | }); 93 | builder.setCancelable(true); 94 | alert.show(); 95 | } 96 | 97 | private void setDisplayHomeAsUpEnabled() { 98 | ActionBar actionBar = getSupportActionBar(); 99 | if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/EditFavoritesFragment.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.app.ListFragment; 4 | import android.os.Bundle; 5 | import android.widget.ArrayAdapter; 6 | import android.widget.ListView; 7 | import org.passwordmaker.android.widgets.SwipeDismissListViewTouchListener; 8 | 9 | public class EditFavoritesFragment extends ListFragment implements SwipeDismissListViewTouchListener.DismissCallbacks { 10 | 11 | private ArrayAdapter favorites; 12 | // We require to keep a reference for the listener 13 | @SuppressWarnings("FieldCanBeLocal") 14 | private SwipeDismissListViewTouchListener touchListener; 15 | 16 | 17 | @Override 18 | public void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | 21 | favorites = new ArrayAdapter( 22 | getActivity(), 23 | android.R.layout.simple_list_item_activated_1, 24 | android.R.id.text1, PwmApplication.getInstance().getAccountManager().getFavoriteUrls()); 25 | setListAdapter(favorites); 26 | } 27 | 28 | @Override 29 | public void onStart() { 30 | super.onStart(); 31 | ListView listView = getListView(); 32 | touchListener = new SwipeDismissListViewTouchListener(listView, this); 33 | listView.setOnTouchListener(touchListener); 34 | listView.setOnScrollListener(touchListener.makeScrollListener()); 35 | } 36 | 37 | public void addItem(String title) { 38 | favorites.add(title); 39 | } 40 | 41 | @Override 42 | public boolean canDismiss(int position) { 43 | return true; 44 | } 45 | 46 | @Override 47 | public void onDismiss(ListView listView, int[] reverseSortedPositions) { 48 | for (int position : reverseSortedPositions) { 49 | favorites.remove(favorites.getItem(position)); 50 | } 51 | favorites.notifyDataSetChanged(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/ImportExportRdf.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.text.ClipboardManager; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.CheckBox; 10 | import android.widget.TextView; 11 | import android.widget.Toast; 12 | import com.google.common.base.Function; 13 | import com.google.common.base.Joiner; 14 | import com.google.common.collect.Iterables; 15 | import org.daveware.passwordmaker.Database; 16 | import org.daveware.passwordmaker.IncompatibleException; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | 22 | public class ImportExportRdf extends Activity { 23 | private static final String LOG_TAG = Logtags.IMPORT_EXPORT_RDF.getTag(); 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_import_export_rdf); 29 | getImportButton().setOnClickListener(new View.OnClickListener() { 30 | @Override 31 | public void onClick(View v) { 32 | onImportClick(); 33 | } 34 | }); 35 | getExportButton().setOnClickListener(new View.OnClickListener() { 36 | @Override 37 | public void onClick(View v) { 38 | onExportClick(); 39 | } 40 | }); 41 | } 42 | 43 | @SuppressWarnings("deprecation") 44 | private void onExportClick() { 45 | try { 46 | String str = PwmApplication.getInstance().serializeSettingsWithOutMasterPassword(); 47 | final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); 48 | clipboard.setText(str); 49 | Toast.makeText(this, "Exported profiles to clipboard", Toast.LENGTH_SHORT).show(); 50 | } catch (Exception e) { 51 | Log.e(LOG_TAG, "Error exporting database", e); 52 | Toast.makeText(this, "Error exporting to RDF", Toast.LENGTH_SHORT).show(); 53 | } 54 | } 55 | 56 | @SuppressWarnings("deprecation") 57 | private void onImportClick() { 58 | final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); 59 | if (!clipboard.hasText()) { 60 | Toast.makeText(this, "No text in clipboard to export", Toast.LENGTH_SHORT).show(); 61 | return; 62 | } 63 | try { 64 | String str = clipboard.getText().toString(); 65 | List errors = new ArrayList(); 66 | Database db = PwmApplication.getInstance().deserializedSettings(str, convertIsChecked(), errors); 67 | PwmApplication.getInstance().getAccountManager().getPwmProfiles().swapAccounts(db); 68 | PwmApplication.getInstance().loadFavoritesFromGlobalSettings(); 69 | String originalInstructions = getResources().getString(R.string.lblImportInstructions); 70 | if ( !errors.isEmpty()) { 71 | originalInstructions += "Accounts not imported: \n" + convertToString(errors); 72 | } 73 | getInstructionsView().setText(originalInstructions); 74 | Toast.makeText(this, "Successfully imported RDF from clipboard", Toast.LENGTH_SHORT).show(); 75 | } catch (Exception e) { 76 | Log.e(LOG_TAG, "Error importing database", e); 77 | Toast.makeText(this, "Error importing RDF from clipboard", Toast.LENGTH_SHORT).show(); 78 | } 79 | } 80 | 81 | protected TextView getInstructionsView() { 82 | return (TextView)findViewById(R.id.lblInstructions); 83 | } 84 | 85 | protected Button getImportButton() { 86 | return (Button)findViewById(R.id.btnImport); 87 | } 88 | 89 | protected Button getExportButton() { 90 | return (Button)findViewById(R.id.btnExport); 91 | } 92 | 93 | protected boolean convertIsChecked() { 94 | CheckBox chkBox = (CheckBox)findViewById(R.id.chkConvertBadAlgo); 95 | return chkBox.isChecked(); 96 | } 97 | 98 | protected String convertToString(List errors) { 99 | Iterable errorStrs = Iterables.transform(errors, new Function() { 100 | @Override 101 | public String apply(IncompatibleException input) { 102 | String msg = input.getMessage(); 103 | int loc = msg.indexOf(':', msg.indexOf(':') + 1); 104 | return msg.substring(0, loc); 105 | } 106 | }); 107 | return Joiner.on("\n").join(errorStrs); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/Logtags.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | public enum Logtags { 4 | 5 | 6 | MAIN_ACTIVITY("MAIN"), 7 | ACCOUNT_DETAIL_ACTIVITY("ADA"), 8 | ACCOUNT_DETAIL_FRAGMENT("ADF"), 9 | ACCOUNT_LIST_ACTIVITY("ALA"), 10 | ACCOUNT_LIST_FRAGMENT("ALF"), 11 | CLASSIC_SETTINGS_IMPORTER("CSI"), 12 | IMPORT_EXPORT_RDF("IMEX"), 13 | PATTERN_DATA_DETAIL_ACTIVITY("PDDA"), 14 | PATTERN_DATA_DETAIL_FRAGMENT("PDDF"), 15 | PATTERN_DATA_LIST_ACTIVITY("PDLA"), 16 | PATTERN_DATA_LIST_FRAGMENT("PDLF"), 17 | PWM_APPLICATION("PAPP") 18 | ; 19 | 20 | private final String tag; 21 | Logtags(String tag) { 22 | this.tag = "PWM/" + tag; 23 | } 24 | 25 | 26 | public String getTag() { 27 | return tag; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/PatternDataDetailActivity.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.app.ActionBar; 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.support.v4.app.NavUtils; 8 | import android.view.MenuItem; 9 | import com.google.common.base.Preconditions; 10 | 11 | 12 | /** 13 | * An activity representing a single PatternData detail screen. This 14 | * activity is only used on handset devices. On tablet-size devices, 15 | * item details are presented side-by-side with a list of items 16 | * in a {@link PatternDataListActivity}. 17 | *

18 | * This activity is mostly just a 'shell' activity containing nothing 19 | * more than a {@link PatternDataDetailFragment}. 20 | */ 21 | public class PatternDataDetailActivity extends Activity { 22 | 23 | private String accountId; 24 | 25 | @Override 26 | protected void onCreate(Bundle savedInstanceState) { 27 | super.onCreate(savedInstanceState); 28 | setContentView(R.layout.activity_patterndata_detail); 29 | 30 | // Show the Up button in the action bar. 31 | setDisplayHomeAsUpEnabled(); 32 | 33 | // savedInstanceState is non-null when there is fragment state 34 | // saved from previous configurations of this activity 35 | // (e.g. when rotating the screen from portrait to landscape). 36 | // In this case, the fragment will automatically be re-added 37 | // to its container so we don't need to manually add it. 38 | // For more information, see the Fragments API guide at: 39 | // 40 | // http://developer.android.com/guide/components/fragments.html 41 | // 42 | if (savedInstanceState == null) { 43 | // Create the detail fragment and add it to the activity 44 | // using a fragment transaction. 45 | Bundle arguments = new Bundle(); 46 | accountId = getIntent().getStringExtra(PatternDataDetailFragment.ARG_ITEM_ID); 47 | Preconditions.checkNotNull(accountId, "%s was not set in PatternDatDetailActivity: %s", 48 | PatternDataDetailFragment.ARG_ITEM_ID, accountId); 49 | arguments.putString(PatternDataDetailFragment.ARG_ITEM_ID, 50 | getIntent().getStringExtra(PatternDataDetailFragment.ARG_ITEM_ID)); 51 | arguments.putBoolean(PatternDataDetailFragment.ARG_TWO_PANE_MODE, false); 52 | arguments.putInt(PatternDataDetailFragment.ARG_PATTERN_POSITION, 53 | getIntent().getIntExtra(PatternDataDetailFragment.ARG_PATTERN_POSITION, 0)); 54 | PatternDataDetailFragment fragment = new PatternDataDetailFragment(); 55 | fragment.setArguments(arguments); 56 | getFragmentManager().beginTransaction() 57 | .add(R.id.patterndata_detail_container, fragment) 58 | .commit(); 59 | } 60 | } 61 | 62 | @Override 63 | public boolean onOptionsItemSelected(MenuItem item) { 64 | int id = item.getItemId(); 65 | if (id == android.R.id.home) { 66 | navigateUp(); 67 | return true; 68 | } 69 | return super.onOptionsItemSelected(item); 70 | } 71 | 72 | private void setDisplayHomeAsUpEnabled() { 73 | // prevent the possible nullpointer if getActionBar returns null. 74 | ActionBar actionBar = getActionBar(); 75 | if ( actionBar != null ) actionBar.setDisplayHomeAsUpEnabled(true); 76 | } 77 | 78 | public void navigateUp() { 79 | 80 | Intent intent = new Intent(this, PatternDataListActivity.class); 81 | intent.putExtra(PatternDataListFragment.ARG_ACCOUNT_ID, accountId); 82 | NavUtils.navigateUpTo(this, intent); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/PatternDataDetailFragment.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.app.Fragment; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.Button; 9 | import android.widget.CheckBox; 10 | import android.widget.RadioButton; 11 | import android.widget.TextView; 12 | import org.daveware.passwordmaker.Account; 13 | import org.daveware.passwordmaker.AccountManager; 14 | import org.daveware.passwordmaker.AccountPatternData; 15 | import org.daveware.passwordmaker.AccountPatternType; 16 | import org.jetbrains.annotations.NotNull; 17 | 18 | /** 19 | * A fragment representing a single PatternData detail screen. 20 | * This fragment is either contained in a {@link PatternDataListActivity} 21 | * in two-pane mode (on tablets) or a {@link PatternDataDetailActivity} 22 | * on handsets. 23 | */ 24 | @SuppressWarnings("ConstantConditions") 25 | public class PatternDataDetailFragment extends Fragment { 26 | /** 27 | * The fragment argument representing the item ID that this fragment 28 | * represents. 29 | */ 30 | public static final String ARG_ITEM_ID = "item_id"; 31 | public static final String ARG_PATTERN_POSITION = "pattern_position"; 32 | public static final String ARG_TWO_PANE_MODE = "two_pane_mode"; 33 | 34 | @SuppressWarnings("FieldCanBeLocal") 35 | private Account account; 36 | private AccountPatternData patternData; 37 | private boolean twoPaneMode; 38 | 39 | /** 40 | * Mandatory empty constructor for the fragment manager to instantiate the 41 | * fragment (e.g. upon screen orientation changes). 42 | */ 43 | public PatternDataDetailFragment() { 44 | } 45 | 46 | @Override 47 | public void onCreate(Bundle savedInstanceState) { 48 | super.onCreate(savedInstanceState); 49 | 50 | if (getArguments().containsKey(ARG_ITEM_ID)) { 51 | // Load the dummy content specified by the fragment 52 | // arguments. In a real-world scenario, use a Loader 53 | // to load content from a content provider. 54 | String itemId = getArguments().getString(ARG_ITEM_ID); 55 | int patternPosition = getArguments().getInt(ARG_PATTERN_POSITION, 0); 56 | AccountManager accountManager = PwmApplication.getInstance().getAccountManager(); 57 | account = accountManager.getPwmProfiles().findAccountById(itemId); 58 | patternData = account.getPatterns().get(patternPosition); 59 | twoPaneMode = getArguments().getBoolean(ARG_TWO_PANE_MODE, false); 60 | } 61 | } 62 | 63 | @Override 64 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 65 | Bundle savedInstanceState) { 66 | return inflater.inflate(R.layout.fragment_patterndata_detail, container, false); 67 | } 68 | 69 | @Override 70 | public void onViewCreated(View view, Bundle savedInstanceState) { 71 | super.onViewCreated(view, savedInstanceState); 72 | 73 | // Show the dummy content as text in a TextView. 74 | if (patternData != null) { 75 | setPatternData(patternData); 76 | } 77 | getPrimaryButton().setOnClickListener(new View.OnClickListener() { 78 | @Override 79 | public void onClick(View v) { 80 | savePatternData(); 81 | if ( ! twoPaneMode ) 82 | ((PatternDataDetailActivity)getActivity()).navigateUp(); 83 | } 84 | }); 85 | 86 | getDomainRegexButton().setOnClickListener(new View.OnClickListener() { 87 | @Override 88 | public void onClick(View v) { 89 | setDomainPattern(); 90 | } 91 | }); 92 | 93 | if ( ! twoPaneMode ) { 94 | getCancelButton().setOnClickListener(new View.OnClickListener() { 95 | @Override 96 | public void onClick(View v) { 97 | ((PatternDataDetailActivity)getActivity()).navigateUp(); 98 | } 99 | }); 100 | } else { 101 | // two pane mode won't have this since you can just change views later 102 | getCancelButton().setVisibility(View.INVISIBLE); 103 | } 104 | } 105 | 106 | protected void setPatternData(@NotNull AccountPatternData pd) { 107 | patternData = pd; 108 | getTextDescription().setText(pd.getDesc()); 109 | getTextPattern().setText(pd.getPattern()); 110 | setPatternType(pd.getType()); 111 | getCheckEnabled().setChecked(pd.isEnabled()); 112 | } 113 | 114 | protected void savePatternData() { 115 | patternData.setDesc(getTextDescription().getText().toString()); 116 | patternData.setPattern(getTextPattern().getText().toString()); 117 | patternData.setType(getPatternType()); 118 | patternData.setEnabled(getCheckEnabled().isChecked()); 119 | } 120 | 121 | protected TextView getTextDescription() { 122 | return (TextView)getView().findViewById(R.id.txtPatternDesc); 123 | } 124 | 125 | protected TextView getTextPattern() { 126 | return (TextView)getView().findViewById(R.id.txtPatternExpression); 127 | } 128 | 129 | protected CheckBox getCheckEnabled() { 130 | return (CheckBox)getView().findViewById(R.id.chkEnabled); 131 | } 132 | 133 | protected Button getPrimaryButton() { 134 | return (Button)getView().findViewById(R.id.primary); 135 | } 136 | 137 | protected Button getCancelButton() { 138 | return (Button)getView().findViewById(android.R.id.closeButton); 139 | } 140 | 141 | protected RadioButton getOptionWildcard() { 142 | return (RadioButton)getView().findViewById(R.id.optWildcard); 143 | } 144 | 145 | protected Button getDomainRegexButton() { 146 | return (Button)getView().findViewById(R.id.btnDomainRegex); 147 | } 148 | 149 | @SuppressWarnings("UnusedDeclaration") 150 | protected RadioButton getOptionRegex() { 151 | return (RadioButton)getView().findViewById(R.id.optRegex); 152 | } 153 | 154 | protected AccountPatternType getPatternType() { 155 | if ( getOptionWildcard().isChecked() ) { 156 | return AccountPatternType.WILDCARD; 157 | } else { 158 | return AccountPatternType.REGEX; 159 | } 160 | } 161 | 162 | protected void setPatternType(AccountPatternType type) { 163 | boolean isWildcard = type == AccountPatternType.WILDCARD; 164 | getOptionWildcard().setChecked(isWildcard); 165 | getOptionWildcard().setChecked(!isWildcard); 166 | } 167 | 168 | protected void setDomainPattern() { 169 | getTextPattern().setText("(.*://)?(.*\\.)?domain\\.com(/.*)?"); 170 | getOptionRegex().setChecked(true); 171 | } 172 | 173 | } 174 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/PatternDataListActivity.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.app.ActionBar; 4 | import android.app.Activity; 5 | import android.content.Intent; 6 | import android.os.Bundle; 7 | import android.support.v4.app.NavUtils; 8 | import android.view.Menu; 9 | import android.view.MenuItem; 10 | import org.daveware.passwordmaker.AccountPatternData; 11 | 12 | 13 | /** 14 | * An activity representing a list of PatternData. This activity 15 | * has different presentations for handset and tablet-size devices. On 16 | * handsets, the activity presents a list of items, which when touched, 17 | * lead to a {@link PatternDataDetailActivity} representing 18 | * item details. On tablets, the activity presents the list of items and 19 | * item details side-by-side using two vertical panes. 20 | *

21 | * The activity makes heavy use of fragments. The list of items is a 22 | * {@link PatternDataListFragment} and the item details 23 | * (if present) is a {@link PatternDataDetailFragment}. 24 | *

25 | * This activity also implements the required 26 | * {@link PatternDataListFragment.Callbacks} interface 27 | * to listen for item selections. 28 | */ 29 | public class PatternDataListActivity extends Activity 30 | implements PatternDataListFragment.Callbacks { 31 | @SuppressWarnings("UnusedDeclaration") 32 | private static final String LOG_TAG = Logtags.PATTERN_DATA_LIST_ACTIVITY.getTag(); 33 | /** 34 | * Whether or not the activity is in two-pane mode, i.e. running on a tablet 35 | * device. 36 | */ 37 | private boolean mTwoPane; 38 | private String accountId; 39 | 40 | @Override 41 | protected void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | setContentView(R.layout.activity_patterndata_list); 44 | // Show the Up button in the action bar. 45 | setDisplayHomeAsUpEnabled(); 46 | 47 | if (findViewById(R.id.patterndata_detail_container) != null) { 48 | // The detail container view will be present only in the 49 | // large-screen layouts (res/values-large and 50 | // res/values-sw600dp). If this view is present, then the 51 | // activity should be in two-pane mode. 52 | mTwoPane = true; 53 | 54 | // In two-pane mode, list items should be given the 55 | // 'activated' state when touched. 56 | getListFragment().setActivateOnItemClick(); 57 | } 58 | } 59 | 60 | @Override 61 | protected void onResume() { 62 | super.onResume(); 63 | accountId = getIntent().getStringExtra(PatternDataListFragment.ARG_ACCOUNT_ID); 64 | getListFragment().setAccountId(accountId); 65 | } 66 | 67 | protected PatternDataListFragment getListFragment() { 68 | return ((PatternDataListFragment) getFragmentManager() 69 | .findFragmentById(R.id.patterndata_list)); 70 | } 71 | 72 | @Override 73 | public boolean onCreateOptionsMenu(Menu menu) { 74 | // Inflate the menu; this adds items to the action bar if it is present. 75 | getMenuInflater().inflate(R.menu.pattern_data_list, menu); 76 | return true; 77 | } 78 | 79 | @Override 80 | public boolean onOptionsItemSelected(MenuItem item) { 81 | int id = item.getItemId(); 82 | if (id == R.id.action_pattern_add) { 83 | getListFragment().createNewPattern(); 84 | return true; 85 | } else if (id == android.R.id.home) { 86 | nagivateUp(); 87 | return true; 88 | } 89 | return super.onOptionsItemSelected(item); 90 | } 91 | 92 | /** 93 | * Callback method from {@link PatternDataListFragment.Callbacks} 94 | * indicating that the item with the given ID was selected. 95 | */ 96 | @Override 97 | public void onItemSelected(int position, AccountPatternData patternData) { 98 | if (mTwoPane) { 99 | // In two-pane mode, show the detail view in this activity by 100 | // adding or replacing the detail fragment using a 101 | // fragment transaction. 102 | Bundle arguments = new Bundle(); 103 | arguments.putString(PatternDataDetailFragment.ARG_ITEM_ID, accountId); 104 | arguments.putBoolean(PatternDataDetailFragment.ARG_TWO_PANE_MODE, mTwoPane); 105 | arguments.putInt(PatternDataDetailFragment.ARG_PATTERN_POSITION, position); 106 | PatternDataDetailFragment fragment = new PatternDataDetailFragment(); 107 | fragment.setArguments(arguments); 108 | getFragmentManager().beginTransaction() 109 | .replace(R.id.patterndata_detail_container, fragment) 110 | .commit(); 111 | 112 | } else { 113 | // In single-pane mode, simply start the detail activity 114 | // for the selected item ID. 115 | Intent detailIntent = new Intent(this, PatternDataDetailActivity.class); 116 | detailIntent.putExtra(PatternDataDetailFragment.ARG_ITEM_ID, accountId); 117 | detailIntent.putExtra(PatternDataDetailFragment.ARG_TWO_PANE_MODE, mTwoPane); 118 | detailIntent.putExtra(PatternDataDetailFragment.ARG_PATTERN_POSITION, position); 119 | startActivity(detailIntent); 120 | } 121 | } 122 | 123 | private void nagivateUp() { 124 | Intent intent; 125 | if ( mTwoPane ) { 126 | intent = new Intent(this, AccountListActivity.class); 127 | 128 | } else { 129 | intent = new Intent(this, AccountDetailActivity.class); 130 | } 131 | if (accountId != null) { 132 | intent.putExtra(AccountDetailFragment.ARG_ITEM_ID, accountId); 133 | } 134 | NavUtils.navigateUpTo(this, intent); 135 | } 136 | 137 | private void setDisplayHomeAsUpEnabled() { 138 | // prevent the possible nullpointer if getActionBar returns null. 139 | ActionBar actionBar = getActionBar(); 140 | if ( actionBar != null ) actionBar.setDisplayHomeAsUpEnabled(true); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/PatternDataListFragment.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.app.ListFragment; 6 | import android.content.DialogInterface; 7 | import android.os.Bundle; 8 | import android.view.View; 9 | import android.widget.ArrayAdapter; 10 | import android.widget.ListView; 11 | import com.google.common.base.Preconditions; 12 | import org.daveware.passwordmaker.Account; 13 | import org.daveware.passwordmaker.AccountPatternData; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.passwordmaker.android.widgets.SwipeDismissListViewTouchListener; 16 | 17 | /** 18 | * A list fragment representing a list of PatternData. This fragment 19 | * also supports tablet devices by allowing list items to be given an 20 | * 'activated' state upon selection. This helps indicate which item is 21 | * currently being viewed in a {@link PatternDataDetailFragment}. 22 | *

23 | * Activities containing this fragment MUST implement the {@link Callbacks} 24 | * interface. 25 | */ 26 | public class PatternDataListFragment extends ListFragment 27 | implements SwipeDismissListViewTouchListener.DismissCallbacks { 28 | 29 | /** 30 | * The serialization (saved instance state) Bundle key representing the 31 | * activated item position. Only used on tablets. 32 | */ 33 | private static final String STATE_ACTIVATED_POSITION = "activated_position"; 34 | public static final String ARG_ACCOUNT_ID = "account_id"; 35 | 36 | /** 37 | * The fragment's current callback object, which is notified of list item 38 | * clicks. 39 | */ 40 | private Callbacks mCallbacks = sDummyCallbacks; 41 | 42 | /** 43 | * The current activated item position. Only used on tablets. 44 | */ 45 | private int mActivatedPosition = ListView.INVALID_POSITION; 46 | @SuppressWarnings("FieldCanBeLocal") 47 | private Account account; 48 | // We need to keep a reference to this 49 | @SuppressWarnings("FieldCanBeLocal") 50 | private SwipeDismissListViewTouchListener touchListener; 51 | 52 | public void setAccountId(@NotNull String accountId) { 53 | account = PwmApplication.getInstance().getAccountManager().getPwmProfiles().findAccountById(accountId); 54 | Preconditions.checkNotNull(account, "Can not find account by id: %s", accountId); 55 | setListAdapter(new ArrayAdapter( 56 | getActivity(), 57 | android.R.layout.simple_list_item_activated_1, 58 | android.R.id.text1, account.getPatterns())); 59 | } 60 | 61 | /** 62 | * A callback interface that all activities containing this fragment must 63 | * implement. This mechanism allows activities to be notified of item 64 | * selections. 65 | */ 66 | public interface Callbacks { 67 | /** 68 | * Callback for when an item has been selected. 69 | */ 70 | public void onItemSelected(int position, AccountPatternData patternData); 71 | } 72 | 73 | /** 74 | * A dummy implementation of the {@link Callbacks} interface that does 75 | * nothing. Used only when this fragment is not attached to an activity. 76 | */ 77 | private final static Callbacks sDummyCallbacks = new Callbacks() { 78 | @Override 79 | public void onItemSelected(int position, AccountPatternData patternData) { 80 | } 81 | }; 82 | 83 | /** 84 | * Mandatory empty constructor for the fragment manager to instantiate the 85 | * fragment (e.g. upon screen orientation changes). 86 | */ 87 | public PatternDataListFragment() { 88 | } 89 | 90 | @Override 91 | public void onViewCreated(View view, Bundle savedInstanceState) { 92 | super.onViewCreated(view, savedInstanceState); 93 | 94 | // Restore the previously serialized activated item position. 95 | if (savedInstanceState != null 96 | && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) { 97 | setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION)); 98 | } 99 | } 100 | 101 | @Override 102 | public void onAttach(Activity activity) { 103 | super.onAttach(activity); 104 | 105 | // Activities containing this fragment must implement its callbacks. 106 | if (!(activity instanceof Callbacks)) { 107 | throw new IllegalStateException("Activity must implement fragment's callbacks."); 108 | } 109 | 110 | if ( getArguments() != null ) { 111 | String accountId = getArguments().getString(ARG_ACCOUNT_ID); 112 | setAccountId(accountId); 113 | } 114 | 115 | mCallbacks = (Callbacks) activity; 116 | } 117 | 118 | @Override 119 | public void onDetach() { 120 | super.onDetach(); 121 | 122 | // Reset the active callbacks interface to the dummy implementation. 123 | mCallbacks = sDummyCallbacks; 124 | } 125 | 126 | @Override 127 | public void onListItemClick(ListView listView, View view, int position, long id) { 128 | super.onListItemClick(listView, view, position, id); 129 | mActivatedPosition = position; 130 | mCallbacks.onItemSelected(position, getPatternAdapter().getItem(position)); 131 | } 132 | 133 | @Override 134 | public void onSaveInstanceState(Bundle outState) { 135 | super.onSaveInstanceState(outState); 136 | if (mActivatedPosition != ListView.INVALID_POSITION) { 137 | // Serialize and persist the activated item position. 138 | outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition); 139 | } 140 | } 141 | 142 | @Override 143 | public void onStart() { 144 | super.onStart(); 145 | ListView listView = getListView(); 146 | touchListener = new SwipeDismissListViewTouchListener(listView, this); 147 | listView.setOnTouchListener(touchListener); 148 | listView.setOnScrollListener(touchListener.makeScrollListener()); 149 | } 150 | 151 | public void createNewPattern() { 152 | getPatternAdapter().add(new AccountPatternData()); 153 | int position = getPatternAdapter().getCount()-1; 154 | setActivatedPosition(position); 155 | mCallbacks.onItemSelected(position, getPatternAdapter().getItem(position)); 156 | } 157 | 158 | protected ArrayAdapter getPatternAdapter() { 159 | 160 | @SuppressWarnings("unchecked") 161 | ArrayAdapter result = (ArrayAdapter)getListAdapter(); 162 | return result; 163 | } 164 | 165 | /** 166 | * Turns on activate-on-click mode. When this mode is on, list items will be 167 | * given the 'activated' state when touched. 168 | */ 169 | public void setActivateOnItemClick() { 170 | // When setting CHOICE_MODE_SINGLE, ListView will automatically 171 | // give items the 'activated' state when touched. 172 | getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); 173 | } 174 | 175 | @Override 176 | public boolean canDismiss(int position) { 177 | return true; 178 | } 179 | 180 | @Override 181 | public void onDismiss(ListView listView, int[] reverseSortedPositions) { 182 | if ( reverseSortedPositions.length != 1 ) return; 183 | int position = reverseSortedPositions[0]; 184 | confirmDelete(position); 185 | } 186 | 187 | protected void reallyDelete(final int position) { 188 | ArrayAdapter patterns = getPatternAdapter(); 189 | patterns.remove(patterns.getItem(position)); 190 | patterns.notifyDataSetChanged(); 191 | } 192 | 193 | protected void confirmDelete(final int position) { 194 | final ArrayAdapter patterns = getPatternAdapter(); 195 | AccountPatternData pattern = patterns.getItem(position); 196 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) 197 | .setIcon(android.R.drawable.ic_dialog_alert) 198 | .setTitle(R.string.confirm_delete) 199 | .setMessage(getResources().getText(R.string.delete_confirmation_text) + " '" + pattern.getDesc() + "'") 200 | .setCancelable(true); 201 | builder.setPositiveButton(R.string.delete_button, new DialogInterface.OnClickListener() { 202 | @Override 203 | public void onClick(DialogInterface dialog, int which) { 204 | reallyDelete(position); 205 | } 206 | }); 207 | builder.setNegativeButton(R.string.Cancel, null); 208 | final AlertDialog alert = builder.create(); 209 | alert.show(); 210 | } 211 | 212 | private void setActivatedPosition(int position) { 213 | if (position == ListView.INVALID_POSITION) { 214 | getListView().setItemChecked(mActivatedPosition, false); 215 | } else { 216 | getListView().setItemChecked(position, true); 217 | } 218 | 219 | mActivatedPosition = position; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/PwmApplication.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | import com.google.common.collect.Lists; 6 | import org.daveware.passwordmaker.*; 7 | import org.passwordmaker.AccountManagerSamples; 8 | 9 | import java.io.*; 10 | import java.security.Security; 11 | import java.util.*; 12 | 13 | import static com.google.common.base.Strings.nullToEmpty; 14 | 15 | /** 16 | * The page http://developer.android.com/reference/android/app/Application.html 17 | * suggest that we shouldn't extend from Application unless we need too, so lets give this a whirl and see how far I can 18 | * get without needing to extend from Application. Right now, I don't need to know anything else about the application 19 | * lifestyle. 20 | * 21 | * This should only be used from the application's UI thread. As both this class, and AccountManager isn't thread safe. 22 | * E.g. any execution point in the application that came from an on::Event:: (e.g. onCreate, onButtonClick, etc). Should 23 | * be very careful not to use this from another thread. Use a method to get an event on the UI thread if need to read/modify 24 | * this data. 25 | * 26 | * See: Activity.runOnUiThread(Runnable) 27 | * View.post(Runnable) 28 | * View.postDelayed(Runnable, long) 29 | * on how to get events to the UI thread from a non-UI thread. Really you should read: 30 | * http://developer.android.com/guide/components/processes-and-threads.html on better examples, for example use of the 31 | * AsyncTask ( http://developer.android.com/reference/android/os/AsyncTask.html ) is probably better use of a background 32 | * task, with something that needs to update something on the UI thread. 33 | * 34 | * 35 | * The reason why this class is lazily loaded, is to ensure that we are created after the android system is setup. Eg. 36 | * the first use of this should be from the Main Activity's onCreate() (or later). 37 | * 38 | * XXX - This class needs some refactoring already. 39 | * 40 | */ 41 | public class PwmApplication { 42 | 43 | private static final String LOG_TAG = Logtags.PWM_APPLICATION.getTag(); 44 | private static final String PROFILE_DB_FILE = "profile_database.rdf"; 45 | 46 | private static PwmApplication sInstance; 47 | private boolean firstTimeLoading = true; 48 | private final AccountManager accountManager; 49 | 50 | 51 | public static PwmApplication getInstance() { 52 | // Lazy load the singleton on first use. 53 | if ( sInstance == null ) { 54 | sInstance = new PwmApplication(); 55 | } 56 | return sInstance; 57 | } 58 | 59 | private PwmApplication() { 60 | PasswordMaker.setDefaultCryptoProvider("SC"); 61 | Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1); 62 | accountManager = new AccountManager(); 63 | AccountManagerSamples.addSamples(accountManager); 64 | } 65 | 66 | public AccountManager getAccountManager() { 67 | return accountManager; 68 | } 69 | 70 | public void saveSettings(Context context) { 71 | String toWrite = serializeSettings(); 72 | FileOutputStream fos = null; 73 | try { 74 | fos = context.openFileOutput(PROFILE_DB_FILE, Context.MODE_PRIVATE); 75 | fos.write(toWrite.getBytes()); 76 | Log.i(LOG_TAG, "Saved application settings"); 77 | } catch (FileNotFoundException e) { 78 | throw new RuntimeException(e); 79 | } catch (IOException e) { 80 | throw new RuntimeException(e); 81 | } finally { 82 | if (fos != null) { 83 | try { 84 | fos.close(); 85 | } catch (IOException e) { 86 | Log.e(LOG_TAG, "Unable to close profile_database.rdf after writing", e); 87 | } 88 | } 89 | } 90 | } 91 | 92 | protected void loadSettings(Context context) { 93 | 94 | LoadResults results = loadClassic(context); 95 | if ( results == null ) results = loadFromRDF(context); 96 | // this only happens if we successfully loaded up the db 97 | if ( results != null ) { 98 | Log.i(LOG_TAG, "Loaded application settings"); 99 | accountManager.getPwmProfiles().swapAccounts(results.database); 100 | loadFavoritesFromGlobalSettings(); 101 | loadMasterPasswordHashFromGlobalSettings(); 102 | if ( results.favorites != null ) accountManager.addFavoriteUrls(results.favorites); 103 | } 104 | } 105 | 106 | protected String serializeSettings() { 107 | AndroidRDFDatabaseWriter writer = new AndroidRDFDatabaseWriter(); 108 | ByteArrayOutputStream os = new ByteArrayOutputStream(); 109 | try { 110 | updateFavoritesGlobalSettings(); 111 | updateMasterPasswordHash(); 112 | writer.write(os, accountManager.getPwmProfiles()); 113 | return os.toString(); 114 | } catch (Exception e) { 115 | throw new RuntimeException(e); 116 | } 117 | } 118 | 119 | public String serializeSettingsWithOutMasterPassword() { 120 | SecureCharArray hash = cloneArray(accountManager.getCurrentPasswordHash()); 121 | String salt = nullToEmpty(accountManager.getPasswordSalt()); 122 | try { 123 | accountManager.disablePasswordHash(); 124 | return serializeSettings(); 125 | } finally { 126 | try { 127 | accountManager.replaceCurrentPasswordHash(hash, salt); 128 | } catch (Exception ignored) { 129 | } 130 | } 131 | } 132 | 133 | protected SecureCharArray cloneArray(SecureCharArray chars) { 134 | if ( chars != null ) 135 | return new SecureCharArray(chars); 136 | else 137 | return new SecureCharArray(); 138 | } 139 | 140 | protected Database deserializedSettings(InputStream is, boolean convertBuggyAlgo, List errors) { 141 | RDFDatabaseReader reader = new RDFDatabaseReader(); 142 | if ( convertBuggyAlgo ) reader.setBuggyAlgoUseAction(DatabaseReader.BuggyAlgoAction.CONVERT); 143 | try { 144 | return reader.read(is); 145 | } catch (Exception e) { 146 | throw new RuntimeException(e); 147 | } finally { 148 | errors.addAll(reader.getIncompatibleAccounts()); 149 | } 150 | } 151 | 152 | protected Database deserializedSettings(InputStream is, boolean convertBuggyAlgo) { 153 | return deserializedSettings(is, convertBuggyAlgo, new ArrayList()); 154 | } 155 | 156 | public Database deserializedSettings(String serialized, boolean convertBuggyAlgo, List errors) { 157 | ByteArrayInputStream is = new ByteArrayInputStream(serialized.getBytes()); 158 | return deserializedSettings(is, convertBuggyAlgo, errors); 159 | } 160 | 161 | @SuppressWarnings("UnusedDeclaration") 162 | public Database deserializedSettings(String serialized, boolean convertBuggyAlgo) { 163 | ByteArrayInputStream is = new ByteArrayInputStream(serialized.getBytes()); 164 | return deserializedSettings(is, convertBuggyAlgo); 165 | } 166 | 167 | protected void updateFavoritesGlobalSettings() { 168 | String encodedUrls = accountManager.encodeFavoriteUrls(); 169 | accountManager.getPwmProfiles().setGlobalSetting(AndroidGlobalSettings.FAVORITES, encodedUrls); 170 | } 171 | 172 | public void loadFavoritesFromGlobalSettings() { 173 | String encodedUrls = accountManager.getPwmProfiles().getGlobalSetting(AndroidGlobalSettings.FAVORITES); 174 | accountManager.decodeFavoritesUrls(encodedUrls, true); 175 | } 176 | 177 | protected void updateMasterPasswordHash() { 178 | SecureCharArray pwdSCA = accountManager.getCurrentPasswordHash(); 179 | String pwdHash = pwdSCA != null ? new String(accountManager.getCurrentPasswordHash().getData()) : ""; 180 | String pwdSalt = accountManager.getPasswordSalt(); 181 | boolean pwdStore = accountManager.shouldStorePasswordHash(); 182 | if ( pwdStore && pwdHash.length() > 0 && pwdSalt.length() > 0) { 183 | accountManager.getPwmProfiles().setGlobalSetting(AndroidGlobalSettings.MASTER_PASSWORD_HASH, pwdHash); 184 | accountManager.getPwmProfiles().setGlobalSetting(AndroidGlobalSettings.MASTER_PASSWORD_SALT, pwdSalt); 185 | } else { 186 | // ensure its cleared out 187 | accountManager.getPwmProfiles().setGlobalSetting(AndroidGlobalSettings.MASTER_PASSWORD_HASH, ""); 188 | accountManager.getPwmProfiles().setGlobalSetting(AndroidGlobalSettings.MASTER_PASSWORD_SALT, ""); 189 | } 190 | accountManager.getPwmProfiles().setGlobalSetting(AndroidGlobalSettings.STORE_MASTER_PASSWORD_HASH, 191 | Boolean.toString(pwdStore)); 192 | } 193 | 194 | protected void loadMasterPasswordHashFromGlobalSettings() { 195 | boolean pwdStore = Boolean.parseBoolean( 196 | accountManager.getPwmProfiles().getGlobalSetting(AndroidGlobalSettings.STORE_MASTER_PASSWORD_HASH)); 197 | if ( !pwdStore ) { 198 | accountManager.disablePasswordHash(); 199 | } else { 200 | String pwdHash = accountManager.getPwmProfiles().getGlobalSetting(AndroidGlobalSettings.MASTER_PASSWORD_HASH); 201 | String pwdSalt = accountManager.getPwmProfiles().getGlobalSetting(AndroidGlobalSettings.MASTER_PASSWORD_SALT); 202 | accountManager.replaceCurrentPasswordHash(new SecureCharArray(pwdHash), pwdSalt); 203 | } 204 | } 205 | 206 | public void loadSettingsOnce(Context context) { 207 | if ( firstTimeLoading ) { 208 | loadSettings(context); 209 | firstTimeLoading = false; 210 | } 211 | } 212 | 213 | 214 | public Set getAllAccountsUrls() { 215 | Set result = new HashSet(); 216 | getAllSubAccountsUrls(accountManager.getPwmProfiles().getRootAccount(), result); 217 | return result; 218 | } 219 | 220 | private static void getAllSubAccountsUrls(Account account, Set urls) { 221 | if ( isNotEmpty(account.getUrl()) ) { 222 | urls.add(account.getUrl()); 223 | } 224 | if ( account.hasChildren() ) { 225 | for (Account child : account.getChildren() ) { 226 | getAllSubAccountsUrls(child, urls); 227 | } 228 | } 229 | } 230 | 231 | private static boolean isNotEmpty(String s) { 232 | return s != null && !s.isEmpty(); 233 | } 234 | 235 | // This class shouldn't be required, this has bad code smell 236 | private static class LoadResults { 237 | private final Database database; 238 | private final Collection favorites; 239 | 240 | private LoadResults(Database database, Collection favorites) { 241 | this.database = database; 242 | this.favorites = favorites; 243 | } 244 | } 245 | 246 | private LoadResults loadClassic(Context context) { 247 | try { 248 | Collection favorites = Lists.newArrayList(); 249 | Database db = ClassicSettingsImporter.importIntoDatabase(context, favorites); 250 | if ( db != null) 251 | return new LoadResults(db, favorites); 252 | } catch (IOException e) { 253 | Log.e(LOG_TAG, "Error importing classic version database", e); 254 | } 255 | return null; 256 | } 257 | 258 | private LoadResults loadFromRDF(Context context) { 259 | File f = new File(context.getFilesDir(), PROFILE_DB_FILE); 260 | if ( ! f.exists() ) 261 | return null; 262 | if ( ! f.canRead() ) { 263 | Log.e(LOG_TAG, "Can not read settings file: " + f.getAbsolutePath()); 264 | return null; 265 | } 266 | InputStream fis = null; 267 | try { 268 | fis = context.openFileInput(PROFILE_DB_FILE); 269 | return new LoadResults(deserializedSettings(fis, false), null); 270 | } catch (FileNotFoundException e) { 271 | Log.e(LOG_TAG, "Unable to read profile", e); 272 | } finally { 273 | if (fis != null) { 274 | try { 275 | fis.close(); 276 | } catch (IOException e) { 277 | Log.e(LOG_TAG, "Unable to close profile_database.rdf after reading", e); 278 | } 279 | } 280 | } 281 | return null; 282 | } 283 | 284 | } 285 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.app.ActionBar; 4 | import android.app.Activity; 5 | import android.os.Bundle; 6 | import android.support.v4.app.NavUtils; 7 | import android.view.MenuItem; 8 | import org.passwordmaker.android.preferences.SettingsFragment; 9 | 10 | // Unused is suppressed just to have a reference for preferences 11 | @SuppressWarnings("UnusedDeclaration") 12 | public class SettingsActivity extends Activity { 13 | 14 | public static final String KEY_SHOW_USERNAME = "pref_showUsername"; 15 | public static final String KEY_SAVED_LENGTH = "pref_saveInputs"; 16 | public static final String KEY_SHOW_PASS_STRENGTH = "pref_showPasswordStrength"; 17 | public static final String KEY_MASTER_PASSWORD_HASH = "pref_masterPasswordHash"; 18 | public static final String KEY_AUTO_ADD_INPUT_FAVS = "pref_AutoAddTextToFavorites"; 19 | 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | // Show the Up button in the action bar. 24 | setDisplayHomeAsUpEnabled(); 25 | 26 | getFragmentManager().beginTransaction() 27 | .replace(android.R.id.content, new SettingsFragment()) 28 | .commit(); 29 | } 30 | 31 | private void setDisplayHomeAsUpEnabled() { 32 | // prevent the possible nullpointer if getActionBar returns null. 33 | ActionBar actionBar = getActionBar(); 34 | if ( actionBar != null ) actionBar.setDisplayHomeAsUpEnabled(true); 35 | } 36 | 37 | @Override 38 | public boolean onOptionsItemSelected(MenuItem item) { 39 | int id = item.getItemId(); 40 | if (id == android.R.id.home) { 41 | // This ID represents the Home or Up button. In the case of this 42 | // activity, the Up button is shown. Use NavUtils to allow users 43 | // to navigate up one level in the application structure. For 44 | // more details, see the Navigation pattern on Android Design: 45 | // 46 | // http://developer.android.com/design/patterns/navigation.html#up-vs-back 47 | // 48 | NavUtils.navigateUpFromSameTask(this); 49 | return true; 50 | } 51 | return super.onOptionsItemSelected(item); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/TextWatcherAdapter.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android; 2 | 3 | import android.text.Editable; 4 | import android.text.TextWatcher; 5 | 6 | public abstract class TextWatcherAdapter implements TextWatcher { 7 | @Override 8 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 9 | 10 | } 11 | 12 | @Override 13 | public void onTextChanged(CharSequence s, int start, int before, int count) { 14 | 15 | } 16 | 17 | @Override 18 | public void afterTextChanged(Editable s) { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/adapters/SubstringArrayAdapter.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android.adapters; 2 | 3 | import android.content.Context; 4 | import android.widget.ArrayAdapter; 5 | import android.widget.Filter; 6 | import com.google.common.base.Predicate; 7 | import com.google.common.collect.Collections2; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * This class would not be thread safe. Even though ArrayAdapter is. But due to the fact they don't share some of its 14 | * internals, I can't without basically rewriting the entire ArrayAdapter make this thread safe. And since my use case 15 | * does not require it, it is not. 16 | */ 17 | 18 | public class SubstringArrayAdapter extends ArrayAdapter { 19 | private List objects; 20 | private final List original_objects; 21 | private final SubStringFilter myFilter = new SubStringFilter(); 22 | 23 | public SubstringArrayAdapter(Context context, @SuppressWarnings("SameParameterValue") int resource, List objects) { 24 | super(context, resource, objects); 25 | this.original_objects = this.objects = objects; 26 | } 27 | 28 | @SuppressWarnings("UnusedDeclaration") 29 | public SubstringArrayAdapter(Context context, int resource, int textViewResourceId, List objects) { 30 | super(context, resource, textViewResourceId, objects); 31 | this.original_objects = this.objects = objects; 32 | } 33 | 34 | @Override 35 | public Filter getFilter() { 36 | return myFilter; 37 | } 38 | 39 | private class SubStringFilter extends Filter { 40 | @Override 41 | protected FilterResults performFiltering(final CharSequence constraint) { 42 | final FilterResults results = new FilterResults(); 43 | if ( constraint == null || constraint.length() == 0 ) { 44 | results.values = new ArrayList(original_objects); 45 | results.count = original_objects.size(); 46 | return results; 47 | } 48 | final ArrayList matched = new ArrayList( 49 | Collections2.filter(original_objects, new Predicate() { 50 | @Override 51 | public boolean apply(String s) { 52 | return s.contains(constraint); 53 | } 54 | })); 55 | results.values = matched; 56 | results.count = matched.size(); 57 | return results; 58 | } 59 | 60 | @SuppressWarnings("unchecked") 61 | @Override 62 | protected void publishResults(CharSequence constraint, FilterResults results) { 63 | objects = (List) results.values; 64 | 65 | if (results.count > 0) 66 | notifyDataSetChanged(); 67 | else 68 | notifyDataSetInvalidated(); 69 | } 70 | } 71 | @Override 72 | public int getCount() { 73 | return objects.size(); 74 | } 75 | 76 | @Override 77 | public String getItem(int position) { 78 | return objects.get(position); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/preferences/MasterPasswordPreference.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android.preferences; 2 | 3 | import android.content.Context; 4 | import android.preference.DialogPreference; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.EditText; 8 | import android.widget.Toast; 9 | import org.daveware.passwordmaker.AccountManager; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.passwordmaker.android.PwmApplication; 12 | import org.passwordmaker.android.R; 13 | 14 | public class MasterPasswordPreference extends DialogPreference { 15 | private View dlgView; 16 | public MasterPasswordPreference(Context context, AttributeSet attrs) { 17 | super(context, attrs); 18 | setDialogLayoutResource(R.layout.dialog_set_pwd_hash); 19 | setPositiveButtonText(android.R.string.ok); 20 | setNegativeButtonText(android.R.string.cancel); 21 | 22 | setDialogIcon(null); 23 | } 24 | 25 | @Override 26 | protected void onBindDialogView(@NotNull View view) { 27 | super.onBindDialogView(view); 28 | dlgView = view; 29 | } 30 | 31 | @Override 32 | protected void onDialogClosed(boolean positiveResult) { 33 | // When the user selects "OK", persist the new value 34 | if (positiveResult) { 35 | EditText password = (EditText)dlgView.findViewById(R.id.password); 36 | EditText confirmed = (EditText)dlgView.findViewById(R.id.confirm_password); 37 | if ( ! password.getText().toString().equals(confirmed.getText().toString())) { 38 | Toast.makeText(getContext(), "Password Mismatch", Toast.LENGTH_SHORT).show(); 39 | return; 40 | } 41 | AccountManager accountManager = PwmApplication.getInstance().getAccountManager(); 42 | if ( password.getText().length() == 0 ) { 43 | accountManager.disablePasswordHash(); 44 | persistBoolean(false); 45 | } else { 46 | accountManager.setCurrentPasswordHashPassword(password.getText().toString()); 47 | persistBoolean(true); 48 | } 49 | PwmApplication.getInstance().saveSettings(getContext()); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/preferences/SettingsFragment.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android.preferences; 2 | 3 | import android.os.Bundle; 4 | import android.preference.PreferenceFragment; 5 | import org.passwordmaker.android.R; 6 | 7 | public class SettingsFragment extends PreferenceFragment { 8 | @Override 9 | public void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | 12 | // Load the preferences from an XML resource 13 | addPreferencesFromResource(R.xml.preferences); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /passwordmaker/src/main/java/org/passwordmaker/android/xmlwrappers/AndroidXmlStreamWriter.java: -------------------------------------------------------------------------------- 1 | package org.passwordmaker.android.xmlwrappers; 2 | 3 | import org.daveware.passwordmaker.xmlwrappers.XmlIOException; 4 | import org.daveware.passwordmaker.xmlwrappers.XmlStreamWriter; 5 | import org.xmlpull.v1.XmlPullParserException; 6 | import org.xmlpull.v1.XmlPullParserFactory; 7 | import org.xmlpull.v1.XmlSerializer; 8 | 9 | import java.io.IOException; 10 | import java.io.Writer; 11 | import java.util.LinkedList; 12 | 13 | public class AndroidXmlStreamWriter implements XmlStreamWriter { 14 | private XmlSerializer serializer; 15 | private Writer writer; 16 | 17 | private static class Tag { 18 | final String name; 19 | final String namespace; 20 | 21 | public Tag(String name, String namespace) { 22 | this.name = name; 23 | this.namespace = namespace; 24 | } 25 | } 26 | 27 | private Tag rootTag = null; 28 | 29 | private final LinkedList tagStack = new LinkedList(); 30 | 31 | protected Tag addTagToStack(String name, @SuppressWarnings("SameParameterValue") String namespace) throws IOException { 32 | Tag ret = new Tag(name, namespace); 33 | // Stupid XmlSerializer from xml pull does this in the different order as the javax.xml.stream does. 34 | // For namespace declarations, they must happen before the tag is added to the serializer. 35 | if ( tagStack.isEmpty() ) { 36 | if ( rootTag == null ) { 37 | rootTag = ret; 38 | return rootTag; 39 | } else { 40 | serializer.startTag(rootTag.namespace, rootTag.name); 41 | tagStack.push(rootTag); 42 | } 43 | } 44 | tagStack.push(ret); 45 | serializer.startTag(ret.namespace, ret.name); 46 | return ret; 47 | } 48 | 49 | protected void flushRootIfNeeded() throws IOException { 50 | if ( tagStack.isEmpty() && rootTag != null ) { 51 | serializer.startTag(rootTag.namespace, rootTag.name); 52 | tagStack.push(rootTag); 53 | } 54 | } 55 | 56 | protected Tag peekTagStack() { 57 | return tagStack.peek(); 58 | } 59 | 60 | protected Tag popTagStack() { 61 | return tagStack.pop(); 62 | } 63 | 64 | public AndroidXmlStreamWriter(Writer writer) throws XmlIOException { 65 | try { 66 | this.writer = writer; 67 | XmlPullParserFactory factory = XmlPullParserFactory.newInstance( 68 | System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null); 69 | if ( factory == null ) throw new XmlIOException("Can not create an XmlPullFactory"); 70 | serializer = factory.newSerializer(); 71 | serializer.setOutput(writer); 72 | } catch (XmlPullParserException e) { 73 | throw new XmlIOException(e); 74 | } catch (IOException e) { 75 | throw new XmlIOException(e); 76 | } 77 | 78 | } 79 | 80 | @Override 81 | public void writeStartDocument() throws XmlIOException { 82 | try { 83 | serializer.startDocument("UTF-8", null); 84 | } catch (IOException e) { 85 | throw new XmlIOException(e); 86 | } 87 | } 88 | 89 | @Override 90 | public void writeStartDocument(String encoding, String version) throws XmlIOException { 91 | try { 92 | serializer.startDocument(encoding, null); 93 | } catch (IOException e) { 94 | throw new XmlIOException(e); 95 | } 96 | } 97 | 98 | @Override 99 | public void addPrefix(String prefix, String namespace) throws XmlIOException { 100 | try { 101 | serializer.setPrefix(prefix, namespace); 102 | } catch (IOException e) { 103 | throw new XmlIOException(e); 104 | } 105 | } 106 | 107 | 108 | @Override 109 | public void writeStartElement(String name) throws XmlIOException { 110 | try { 111 | addTagToStack(name, ""); 112 | } catch (IOException e) { 113 | throw new XmlIOException(e); 114 | } 115 | } 116 | 117 | @Override 118 | public void writeAttribute(String localName, String value) throws XmlIOException { 119 | try { 120 | // alright, if we are asking to write to an attribute we must be done with writing any namespaces. 121 | flushRootIfNeeded(); 122 | serializer.attribute("", localName, value); 123 | } catch (IOException e) { 124 | throw new XmlIOException(e); 125 | } 126 | } 127 | 128 | @Override 129 | public void writeEndElement() throws XmlIOException { 130 | try { 131 | // if we ask to end our element, and we haven't done anything with the root, let flush that first 132 | flushRootIfNeeded(); 133 | Tag lastTag = peekTagStack(); 134 | serializer.endTag(lastTag.namespace, lastTag.name); 135 | popTagStack(); // only pop if no exception was thrown, this way we can debug. 136 | } catch (IOException e) { 137 | throw new XmlIOException(e); 138 | } 139 | } 140 | 141 | @Override 142 | public void writeEndDocument() throws XmlIOException { 143 | try { 144 | serializer.endDocument(); 145 | } catch (IOException e) { 146 | throw new XmlIOException(e); 147 | } 148 | } 149 | 150 | @Override 151 | public void flush() throws XmlIOException { 152 | try { 153 | serializer.flush(); 154 | } catch (IOException e) { 155 | throw new XmlIOException(e); 156 | } 157 | } 158 | 159 | @Override 160 | public void close() throws XmlIOException { 161 | try { 162 | writer.close(); 163 | } catch (IOException e) { 164 | throw new XmlIOException(e); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-hdpi/ic_action_av_add_to_queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-hdpi/ic_action_av_add_to_queue.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-hdpi/ic_action_collections_labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-hdpi/ic_action_collections_labels.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-hdpi/ic_action_content_import_export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-hdpi/ic_action_content_import_export.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-hdpi/ic_menu_copy_holo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-hdpi/ic_menu_copy_holo_light.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-mdpi/ic_action_av_add_to_queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-mdpi/ic_action_av_add_to_queue.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-mdpi/ic_action_collections_labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-mdpi/ic_action_collections_labels.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-mdpi/ic_action_content_import_export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-mdpi/ic_action_content_import_export.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-mdpi/ic_menu_copy_holo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-mdpi/ic_menu_copy_holo_light.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-xhdpi/ic_action_av_add_to_queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-xhdpi/ic_action_av_add_to_queue.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-xhdpi/ic_action_collections_labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-xhdpi/ic_action_collections_labels.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-xhdpi/ic_action_content_import_export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-xhdpi/ic_action_content_import_export.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-xhdpi/ic_menu_copy_holo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-xhdpi/ic_menu_copy_holo_light.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-xxhdpi/ic_action_av_add_to_queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-xxhdpi/ic_action_av_add_to_queue.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-xxhdpi/ic_action_collections_labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-xxhdpi/ic_action_collections_labels.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-xxhdpi/ic_action_content_import_export.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-xxhdpi/ic_action_content_import_export.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable-xxhdpi/ic_menu_copy_holo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passwordmaker/android-passwordmaker/ad5cbf639c0b9b6bda9c510c483847d0b452c2b3/passwordmaker/src/main/res/drawable-xxhdpi/ic_menu_copy_holo_light.png -------------------------------------------------------------------------------- /passwordmaker/src/main/res/drawable/list_item_account_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /passwordmaker/src/main/res/layout/activity_account_detail.xml: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /passwordmaker/src/main/res/layout/activity_account_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /passwordmaker/src/main/res/layout/activity_account_twopane.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 20 | 21 | 31 | 32 | 39 | 40 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /passwordmaker/src/main/res/layout/activity_edit_favorites.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 20 | 21 | -------------------------------------------------------------------------------- /passwordmaker/src/main/res/layout/activity_import_export_rdf.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 21 |