├── .gitignore
├── README.md
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── ic_launcher-web.png
├── proguard-rules.pro
└── src
├── androidTest
└── java
│ └── org
│ └── mariotaku
│ └── pass
│ └── ApplicationTest.java
├── debug
└── java
│ └── org
│ └── mariotaku
│ └── pass
│ └── util
│ └── DebugModeUtils.java
├── main
├── AndroidManifest.xml
├── assets
│ └── databases
│ │ └── pass.db
├── java
│ └── org
│ │ ├── mariotaku
│ │ └── pass
│ │ │ ├── Constants.java
│ │ │ ├── PassApplication.java
│ │ │ ├── activity
│ │ │ ├── PassGenDialogActivity.java
│ │ │ └── SettingsActivity.java
│ │ │ ├── fragment
│ │ │ ├── AppWindowsListFragment.java
│ │ │ ├── NfcTagsListFragment.java
│ │ │ ├── RememberMasterPasswordFingerprintFragment.java
│ │ │ ├── RememberMasterPasswordPatternFragment.java
│ │ │ └── SettingsDetailsFragment.java
│ │ │ ├── model
│ │ │ └── AccessibilityExtra.java
│ │ │ ├── preference
│ │ │ └── NumberPickerPreference.java
│ │ │ ├── provider
│ │ │ └── PassProvider.java
│ │ │ ├── service
│ │ │ ├── ClipboardRestoreService.java
│ │ │ ├── PasswordHelperService.java
│ │ │ └── QuickSettingsTileService.java
│ │ │ ├── util
│ │ │ ├── ContextCompat.java
│ │ │ ├── FingerprintCryptoHelper.java
│ │ │ ├── FingerprintHelper.java
│ │ │ ├── MasterPasswordEncrypter.java
│ │ │ ├── Utils.java
│ │ │ └── passgen
│ │ │ │ ├── PassGen.java
│ │ │ │ └── impl
│ │ │ │ ├── HotpPinImpl.java
│ │ │ │ ├── PasswordComposerImpl.java
│ │ │ │ └── SuperGenPassImpl.java
│ │ │ └── view
│ │ │ ├── PasswordContainer.java
│ │ │ ├── RememberMasterPasswordContainer.java
│ │ │ └── controller
│ │ │ ├── PatternPasswordController.java
│ │ │ ├── RememberPasswordFingerprintController.java
│ │ │ ├── RememberPasswordFingerprintNoticeController.java
│ │ │ ├── RememberPasswordInputController.java
│ │ │ ├── RememberPasswordPatternController.java
│ │ │ ├── RememberPasswordPatternNoticeController.java
│ │ │ └── TextPasswordController.java
│ │ └── openauthentication
│ │ └── otp
│ │ └── OneTimePasswordAlgorithm.java
├── res-localized
│ └── values-zh-rCN
│ │ └── strings.xml
└── res
│ ├── anim
│ ├── slide_in_left.xml
│ ├── slide_in_right.xml
│ ├── slide_out_left.xml
│ └── slide_out_right.xml
│ ├── drawable-hdpi
│ ├── ic_fp_40px.png
│ ├── ic_launcher.png
│ ├── ic_pw_40px.png
│ ├── ic_qs_tile_mdpi.png
│ └── ic_stat_password.png
│ ├── drawable-mdpi
│ ├── ic_fp_40px.png
│ ├── ic_launcher.png
│ ├── ic_pw_40px.png
│ ├── ic_qs_tile_mdpi.png
│ └── ic_stat_password.png
│ ├── drawable-xhdpi
│ ├── ic_fp_40px.png
│ ├── ic_launcher.png
│ ├── ic_pw_40px.png
│ ├── ic_qs_tile_mdpi.png
│ └── ic_stat_password.png
│ ├── drawable-xxhdpi
│ ├── ic_fp_40px.png
│ ├── ic_launcher.png
│ ├── ic_pw_40px.png
│ ├── ic_qs_tile_mdpi.png
│ └── ic_stat_password.png
│ ├── drawable-xxxhdpi
│ ├── ic_fp_40px.png
│ ├── ic_launcher.png
│ ├── ic_pw_40px.png
│ └── ic_qs_tile_mdpi.png
│ ├── drawable
│ ├── ic_action_back.xml
│ ├── ic_action_clear.xml
│ ├── ic_action_copy.xml
│ ├── ic_action_info.xml
│ ├── ic_action_settings.xml
│ └── ic_btn_ok.xml
│ ├── layout
│ ├── activity_pass_gen_dialog.xml
│ ├── dialog_add_card.xml
│ ├── fragment_remember_master_password_fingerprint.xml
│ ├── fragment_remember_master_password_pattern.xml
│ ├── layout_dialog_title_pass_gen.xml
│ ├── layout_password_pattern.xml
│ └── layout_password_text.xml
│ ├── menu
│ ├── menu_pass_dialog.xml
│ └── menu_tags_list.xml
│ ├── raw
│ └── two_level_tlds.txt
│ ├── values
│ ├── arrays.xml
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── themes.xml
│ └── xml
│ ├── accessibility_service.xml
│ └── pref_general.xml
├── release
└── java
│ └── org
│ └── mariotaku
│ └── pass
│ └── util
│ └── DebugModeUtils.java
└── test
└── java
└── org
└── mariotaku
└── pass
└── ExampleUnitTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | /build
3 |
4 | # Local configuration file (sdk path, etc)
5 | local.properties
6 |
7 | # Gradle generated files
8 | .gradle/
9 |
10 | # Signing files
11 | .signing/
12 |
13 | # User-specific configurations
14 | /.idea
15 | *.iml
16 |
17 | # OS-specific files
18 | .DS_Store
19 | .DS_Store?
20 | ._*
21 | .Spotlight-V100
22 | .Trashes
23 | ehthumbs.db
24 | Thumbs.db
25 |
26 | # Private files
27 | /signing.properties
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #Pass
2 |
3 | Password Generator for Android
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.1.3'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 |
16 | android {
17 | compileSdkVersion 24
18 | buildToolsVersion "24.0.1"
19 |
20 | defaultConfig {
21 | applicationId "org.mariotaku.pass"
22 | minSdkVersion 21
23 | targetSdkVersion 24
24 | versionCode 3
25 | versionName "0.9.2"
26 | }
27 |
28 | lintOptions {
29 | abortOnError false
30 | }
31 |
32 | sourceSets {
33 | main {
34 | res.srcDirs += project.files("src/$name/res-localized")
35 | }
36 | }
37 |
38 | buildTypes {
39 | release {
40 | minifyEnabled false
41 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
42 | }
43 | }
44 |
45 | buildTypes.each { buildType ->
46 | def file = rootProject.file('signing.properties')
47 | if (file.exists()) {
48 | def cfg = signingConfigs.maybeCreate(buildType.name)
49 | loadSigningConfig(cfg, file)
50 | buildType.signingConfig = cfg
51 | }
52 | }
53 | }
54 |
55 | repositories {
56 | jcenter()
57 | maven { url 'https://jitpack.io/' }
58 | }
59 |
60 | dependencies {
61 | compile 'com.eftimoff:android-patternview:1.0.3@aar'
62 | compile 'com.android.support:support-annotations:24.2.0'
63 | compile 'com.android.support:support-v4:24.2.0'
64 | compile 'com.github.mariotaku:SQLiteQB:e3b8e3b6ac'
65 | compile 'com.squareup.okio:okio:1.6.0'
66 | compile 'com.readystatesoftware.sqliteasset:sqliteassethelper:2.0.1'
67 | debugCompile 'com.facebook.stetho:stetho:1.2.0'
68 | compile fileTree(dir: 'libs', include: ['*.jar'])
69 | testCompile 'junit:junit:4.12'
70 | }
71 |
72 | def loadSigningConfig(def cfg, def file) {
73 | Properties signingProp = new Properties()
74 | signingProp.load(file.newInputStream())
75 | cfg.setStoreFile(new File((String) signingProp.get('storeFile')))
76 | cfg.setStorePassword((String) signingProp.get('storePassword'))
77 | cfg.setKeyAlias((String) signingProp.get('keyAlias'))
78 | cfg.setKeyPassword((String) signingProp.get('keyPassword'))
79 | }
80 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Sep 01 09:57:31 CST 2016
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-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/ic_launcher-web.png
--------------------------------------------------------------------------------
/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/mariotaku/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
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 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/src/androidTest/java/org/mariotaku/pass/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/src/debug/java/org/mariotaku/pass/util/DebugModeUtils.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.util;
2 |
3 | import android.app.Application;
4 |
5 | import com.facebook.stetho.Stetho;
6 |
7 | /**
8 | * Created by mariotaku on 15/11/19.
9 | */
10 | public class DebugModeUtils {
11 |
12 | public static void init(Application application) {
13 | Stetho.initializeWithDefaults(application);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
11 |
21 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
42 |
43 |
46 |
47 |
50 |
51 |
52 |
53 |
56 |
57 |
62 |
63 |
64 |
65 |
66 |
67 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/main/assets/databases/pass.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/assets/databases/pass.db
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/Constants.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass;
2 |
3 | /**
4 | * Created by mariotaku on 15/10/29.
5 | */
6 | public interface Constants {
7 |
8 | String LOGTAG = "Pass";
9 |
10 | String SHARED_PREFERENCES_NAME = "preferences";
11 | String KEY_PATTERN_PASSWORD = "pattern_password";
12 | String KEY_PATTERN_IV = "pattern_iv";
13 | String KEY_FINGERPRINT_PASSWORD = "fingerprint_password";
14 | String KEY_FINGERPRINT_IV = "fingerprint_iv";
15 | String KEY_PASSWORD_SALT = "password_salt";
16 | String KEY_PASSWORD_LENGTH = "password_length";
17 | String KEY_PIN_LENGTH = "pin_length";
18 | String KEY_PASSWORD_GENERATOR_TYPE = "password_generator_type";
19 | String KEY_PASSWORD_RETRY_COUNT = "password_retry_count";
20 | String KEY_CLEAR_COPIED_PASSWORD_AUTOMATICALLY = "clear_copied_password_automatically";
21 | String KEY_PASSWORD_INCLUDE_SPECIAL_CHARACTERS = "password_include_special_characters";
22 |
23 | String KEY_COPIED_PASSWORD_VALIDITY = "copied_password_validity";
24 | String ACTION_TAG_DISCOVERED = BuildConfig.APPLICATION_ID + ".TAG_DISCOVERED";
25 |
26 | String ACTION_PASSWORD_CALLBACK = BuildConfig.APPLICATION_ID + ".PASSWORD_CALLBACK";
27 | String EXTRA_RESID = "resid";
28 |
29 | String EXTRA_ACCESSIBILITY_EXTRA = "accessibility_extra";
30 |
31 | int REQUEST_REQUEST_PERMISSION = 101;
32 | int NOTIFICATION_ID_CLIPBOARD = 100;
33 | int NOTIFICATION_ID_ACCESSIBILITY = 101;
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/PassApplication.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass;
2 |
3 | import android.app.Application;
4 |
5 | import org.mariotaku.pass.util.DebugModeUtils;
6 |
7 | public class PassApplication extends Application implements Constants {
8 | public void onCreate() {
9 | super.onCreate();
10 | DebugModeUtils.init(this);
11 | }
12 | }
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/activity/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.activity;
2 |
3 | import android.app.ActionBar;
4 | import android.app.Fragment;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.preference.PreferenceActivity;
8 |
9 | import org.mariotaku.pass.Constants;
10 | import org.mariotaku.pass.R;
11 | import org.mariotaku.pass.fragment.SettingsDetailsFragment;
12 |
13 | /**
14 | * Created by mariotaku on 15/10/31.
15 | */
16 | public class SettingsActivity extends PreferenceActivity implements Constants {
17 |
18 | @Override
19 | protected void onCreate(final Bundle savedInstanceState) {
20 | final Intent intent = getIntent();
21 | if (!intent.hasExtra(EXTRA_SHOW_FRAGMENT) && !intent.hasExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS)) {
22 | final Bundle fragmentArgs = new Bundle();
23 | fragmentArgs.putInt(EXTRA_RESID, R.xml.pref_general);
24 | intent.putExtra(EXTRA_SHOW_FRAGMENT, SettingsDetailsFragment.class.getName());
25 | intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentArgs);
26 | }
27 | super.onCreate(savedInstanceState);
28 |
29 | final ActionBar actionBar = getActionBar();
30 | if (actionBar != null) {
31 | actionBar.setDisplayHomeAsUpEnabled(true);
32 | }
33 | }
34 |
35 | @Override
36 | protected boolean isValidFragment(final String fragmentName) {
37 | try {
38 | return Fragment.class.isAssignableFrom(Class.forName(fragmentName));
39 | } catch (ClassNotFoundException e) {
40 | return false;
41 | }
42 | }
43 |
44 | @Override
45 | public boolean onNavigateUp() {
46 | finish();
47 | return true;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/fragment/AppWindowsListFragment.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.fragment;
2 |
3 | import android.app.ListFragment;
4 | import android.app.LoaderManager;
5 | import android.content.CursorLoader;
6 | import android.content.Loader;
7 | import android.database.Cursor;
8 | import android.os.Bundle;
9 | import android.widget.SimpleCursorAdapter;
10 |
11 | import org.mariotaku.pass.Constants;
12 | import org.mariotaku.pass.provider.PassProvider.PassDataStore.AppWindows;
13 |
14 | /**
15 | * Created by mariotaku on 15/11/17.
16 | */
17 | public class AppWindowsListFragment extends ListFragment implements Constants, LoaderManager.LoaderCallbacks {
18 |
19 | @Override
20 | public void onActivityCreated(final Bundle savedInstanceState) {
21 | super.onActivityCreated(savedInstanceState);
22 | setListAdapter(new SimpleCursorAdapter(getActivity(),
23 | android.R.layout.simple_list_item_activated_2, null,
24 | new String[]{AppWindows.WINDOW_NAME, AppWindows.DOMAIN},
25 | new int[]{android.R.id.text1, android.R.id.text2}, 0));
26 | getLoaderManager().initLoader(0, null, this);
27 | setListShownNoAnimation(false);
28 | }
29 |
30 | @Override
31 | public Loader onCreateLoader(final int id, final Bundle args) {
32 | return new CursorLoader(getActivity(), AppWindows.CONTENT_URI, AppWindows.COLUMNS, null, null, null);
33 | }
34 |
35 | @Override
36 | public void onLoadFinished(final Loader loader, final Cursor data) {
37 | ((SimpleCursorAdapter) getListAdapter()).changeCursor(data);
38 | setListShown(true);
39 | }
40 |
41 | @Override
42 | public void onLoaderReset(final Loader loader) {
43 | ((SimpleCursorAdapter) getListAdapter()).changeCursor(null);
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/fragment/NfcTagsListFragment.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.fragment;
2 |
3 | import android.app.Activity;
4 | import android.app.AlertDialog;
5 | import android.app.Dialog;
6 | import android.app.DialogFragment;
7 | import android.app.ListFragment;
8 | import android.app.LoaderManager;
9 | import android.app.PendingIntent;
10 | import android.content.BroadcastReceiver;
11 | import android.content.ContentValues;
12 | import android.content.Context;
13 | import android.content.CursorLoader;
14 | import android.content.DialogInterface;
15 | import android.content.Intent;
16 | import android.content.IntentFilter;
17 | import android.content.Loader;
18 | import android.database.Cursor;
19 | import android.nfc.NfcAdapter;
20 | import android.nfc.Tag;
21 | import android.os.Bundle;
22 | import android.text.Editable;
23 | import android.text.TextUtils;
24 | import android.text.TextWatcher;
25 | import android.view.Menu;
26 | import android.view.MenuInflater;
27 | import android.widget.Button;
28 | import android.widget.EditText;
29 | import android.widget.SimpleCursorAdapter;
30 | import android.widget.Toast;
31 |
32 | import org.mariotaku.pass.BuildConfig;
33 | import org.mariotaku.pass.Constants;
34 | import org.mariotaku.pass.R;
35 | import org.mariotaku.pass.provider.PassProvider.PassDataStore.NfcTags;
36 | import org.mariotaku.sqliteqb.library.Expression;
37 |
38 | import okio.ByteString;
39 |
40 | /**
41 | * Created by mariotaku on 15/11/17.
42 | */
43 | public class NfcTagsListFragment extends ListFragment implements Constants, LoaderManager.LoaderCallbacks {
44 |
45 | private BroadcastReceiver mNfcReceiver = new BroadcastReceiver() {
46 |
47 | @Override
48 | public void onReceive(final Context context, final Intent intent) {
49 | final Tag tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
50 | final byte[] id = tag.getId();
51 | if (id.length > 0) {
52 | final String where = Expression.equalsArgs(NfcTags.TAG_ID).getSQL();
53 | final String[] whereArgs = {ByteString.of(id).hex()};
54 | try (Cursor cursor = getActivity().getContentResolver().query(NfcTags.CONTENT_URI,
55 | NfcTags.COLUMNS, where, whereArgs, null)) {
56 | if (cursor != null && cursor.moveToFirst()) {
57 | } else {
58 | showAddTagDialog(tag);
59 | }
60 | }
61 | } else {
62 | Toast.makeText(getActivity(), R.string.unsupported_tag, Toast.LENGTH_SHORT).show();
63 | }
64 | }
65 | };
66 |
67 | private void showAddTagDialog(final Tag tag) {
68 | final AddCardDialogFragment df = new AddCardDialogFragment();
69 | final Bundle args = new Bundle();
70 | args.putParcelable(NfcAdapter.EXTRA_TAG, tag);
71 | df.setArguments(args);
72 | df.show(getFragmentManager(), "add_card");
73 | }
74 |
75 | @Override
76 | public void onActivityCreated(final Bundle savedInstanceState) {
77 | super.onActivityCreated(savedInstanceState);
78 | setHasOptionsMenu(true);
79 | setListAdapter(new SimpleCursorAdapter(getActivity(),
80 | android.R.layout.simple_list_item_activated_2, null,
81 | new String[]{NfcTags.NAME, NfcTags.DOMAIN},
82 | new int[]{android.R.id.text1, android.R.id.text2}, 0));
83 | getLoaderManager().initLoader(0, null, this);
84 | setListShownNoAnimation(false);
85 | }
86 |
87 | @Override
88 | public Loader onCreateLoader(final int id, final Bundle args) {
89 | return new CursorLoader(getActivity(), NfcTags.CONTENT_URI, NfcTags.COLUMNS, null, null, null);
90 | }
91 |
92 | @Override
93 | public void onLoadFinished(final Loader loader, final Cursor data) {
94 | ((SimpleCursorAdapter) getListAdapter()).changeCursor(data);
95 | setListShown(true);
96 | }
97 |
98 | @Override
99 | public void onLoaderReset(final Loader loader) {
100 | ((SimpleCursorAdapter) getListAdapter()).changeCursor(null);
101 | }
102 |
103 | @Override
104 | public void onPause() {
105 | super.onPause();
106 | final Activity activity = getActivity();
107 | final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
108 | if (nfcAdapter != null && nfcAdapter.isEnabled()) {
109 | nfcAdapter.disableForegroundDispatch(activity);
110 | }
111 | activity.unregisterReceiver(mNfcReceiver);
112 | }
113 |
114 | @Override
115 | public void onResume() {
116 | final Activity activity = getActivity();
117 | activity.registerReceiver(mNfcReceiver, new IntentFilter(ACTION_TAG_DISCOVERED));
118 | final NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(activity);
119 | if (nfcAdapter != null && nfcAdapter.isEnabled()) {
120 | final Intent intent = new Intent(ACTION_TAG_DISCOVERED);
121 | intent.setPackage(BuildConfig.APPLICATION_ID);
122 | nfcAdapter.enableForegroundDispatch(activity, PendingIntent.getBroadcast(activity,
123 | 0, intent, PendingIntent.FLAG_UPDATE_CURRENT), null, null);
124 | }
125 | super.onResume();
126 | }
127 |
128 | @Override
129 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
130 | inflater.inflate(R.menu.menu_tags_list, menu);
131 | }
132 |
133 | public static class AddCardDialogFragment extends DialogFragment implements DialogInterface.OnClickListener, DialogInterface.OnShowListener, TextWatcher {
134 | @Override
135 | public Dialog onCreateDialog(final Bundle savedInstanceState) {
136 | final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
137 | builder.setTitle(R.string.add_card);
138 | builder.setView(R.layout.dialog_add_card);
139 | builder.setNegativeButton(android.R.string.cancel, null);
140 | builder.setPositiveButton(android.R.string.ok, this);
141 | final AlertDialog dialog = builder.create();
142 | dialog.setOnShowListener(this);
143 | return dialog;
144 | }
145 |
146 | @Override
147 | public void onClick(final DialogInterface dialog, final int which) {
148 | switch (which) {
149 | case DialogInterface.BUTTON_POSITIVE: {
150 | final ContentValues values = new ContentValues();
151 | final AlertDialog alertDialog = (AlertDialog) getDialog();
152 | final EditText editHost = (EditText) alertDialog.findViewById(R.id.edit_host);
153 | final EditText editName = (EditText) alertDialog.findViewById(R.id.edit_name);
154 | final Editable editHostText = editHost.getText(), editNameText = editName.getText();
155 | if (TextUtils.isEmpty(editHostText)) return;
156 | values.put(NfcTags.DOMAIN, editHostText.toString());
157 | values.put(NfcTags.NAME, editNameText.toString());
158 | values.put(NfcTags.TAG_ID, ByteString.of(getNfcTag().getId()).hex());
159 | getActivity().getContentResolver().insert(NfcTags.CONTENT_URI, values);
160 | break;
161 | }
162 | }
163 | }
164 |
165 | public Tag getNfcTag() {
166 | return getArguments().getParcelable(NfcAdapter.EXTRA_TAG);
167 | }
168 |
169 | @Override
170 | public void onShow(final DialogInterface dialog) {
171 | final AlertDialog alertDialog = (AlertDialog) dialog;
172 | final EditText editHost = (EditText) alertDialog.findViewById(R.id.edit_host);
173 | final EditText editName = (EditText) alertDialog.findViewById(R.id.edit_name);
174 | final byte[] id = getNfcTag().getId();
175 | final ByteString bs = ByteString.of(id);
176 | final String idHex = bs.hex();
177 | if (idHex.length() > 8) {
178 | editName.setText(getString(R.string.tag_name_prefix, idHex.substring(idHex.length() - 8)));
179 | } else {
180 | editName.setText(getString(R.string.tag_name_prefix, idHex));
181 | }
182 | editHost.addTextChangedListener(this);
183 |
184 | updatePositiveButton();
185 | }
186 |
187 | @Override
188 | public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
189 |
190 | }
191 |
192 | @Override
193 | public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
194 |
195 | }
196 |
197 | @Override
198 | public void afterTextChanged(final Editable s) {
199 | updatePositiveButton();
200 | }
201 |
202 | private void updatePositiveButton() {
203 | final AlertDialog alertDialog = (AlertDialog) getDialog();
204 | final Button button = alertDialog.getButton(DialogInterface.BUTTON_POSITIVE);
205 | final EditText editHost = (EditText) alertDialog.findViewById(R.id.edit_host);
206 | final EditText editName = (EditText) alertDialog.findViewById(R.id.edit_name);
207 | button.setEnabled(editHost.length() > 0);
208 | }
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/fragment/RememberMasterPasswordFingerprintFragment.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.fragment;
2 |
3 | import android.app.Fragment;
4 | import android.os.Bundle;
5 | import android.support.annotation.NonNull;
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.Button;
10 | import android.widget.Toast;
11 |
12 | import org.mariotaku.pass.Constants;
13 | import org.mariotaku.pass.R;
14 | import org.mariotaku.pass.util.FingerprintHelper;
15 | import org.mariotaku.pass.view.RememberMasterPasswordContainer;
16 |
17 | /**
18 | * Created by mariotaku on 15/10/31.
19 | */
20 | public class RememberMasterPasswordFingerprintFragment extends Fragment implements Constants,
21 | View.OnClickListener, RememberMasterPasswordContainer.PageListener {
22 | private RememberMasterPasswordContainer mViewPages;
23 | private FingerprintHelper mFingerprintHelper;
24 | private Button mPreviousButton, mNextButton;
25 |
26 | @Override
27 | public void onActivityCreated(final Bundle savedInstanceState) {
28 | super.onActivityCreated(savedInstanceState);
29 | mFingerprintHelper = FingerprintHelper.getInstance(getActivity());
30 | mNextButton.setOnClickListener(this);
31 | mPreviousButton.setOnClickListener(this);
32 | mViewPages.setPageListener(this);
33 |
34 | final boolean hasPermission = mFingerprintHelper.requestPermission(this, REQUEST_REQUEST_PERMISSION);
35 | mNextButton.setEnabled(hasPermission);
36 | if (hasPermission) {
37 | checkFingerprintSupport();
38 | }
39 | updatePage();
40 | }
41 |
42 | public FingerprintHelper getFingerprintHelper() {
43 | return mFingerprintHelper;
44 | }
45 |
46 | @Override
47 |
48 | public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions,
49 | @NonNull final int[] grantResults) {
50 | switch (requestCode) {
51 | case REQUEST_REQUEST_PERMISSION: {
52 | final boolean hasPermission = mFingerprintHelper.hasPermission();
53 | mNextButton.setEnabled(hasPermission);
54 | if (hasPermission) {
55 | checkFingerprintSupport();
56 | }
57 | break;
58 | }
59 | }
60 | }
61 |
62 | private void checkFingerprintSupport() {
63 | if (!mFingerprintHelper.isHardwareDetected()) {
64 | Toast.makeText(getActivity(), R.string.fingerprint_not_supported, Toast.LENGTH_LONG).show();
65 | getActivity().finish();
66 | } else if (!mFingerprintHelper.hasEnrolledFingerprints()) {
67 | Toast.makeText(getActivity(), R.string.no_fingerprint_enrolled, Toast.LENGTH_LONG).show();
68 | getActivity().finish();
69 | } else {
70 | mNextButton.setEnabled(true);
71 | }
72 | }
73 |
74 | @Override
75 | public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
76 | return inflater.inflate(R.layout.fragment_remember_master_password_fingerprint, container, false);
77 | }
78 |
79 | @Override
80 | public void onViewCreated(final View view, final Bundle savedInstanceState) {
81 | super.onViewCreated(view, savedInstanceState);
82 | mViewPages = ((RememberMasterPasswordContainer) view.findViewById(R.id.view_pages));
83 | mPreviousButton = (Button) view.findViewById(R.id.previous);
84 | mNextButton = (Button) view.findViewById(R.id.next);
85 | }
86 |
87 | @Override
88 | public void onClick(final View v) {
89 | switch (v.getId()) {
90 | case R.id.next: {
91 | mViewPages.showNext();
92 | break;
93 | }
94 | case R.id.previous: {
95 | mViewPages.showPrevious();
96 | break;
97 | }
98 | }
99 | }
100 |
101 | @Override
102 | public void onPageChanged(final int current) {
103 | updatePage();
104 | }
105 |
106 | private void updatePage() {
107 | final int displayedChild = mViewPages.getDisplayedChild();
108 | if (displayedChild == 0) {
109 | mPreviousButton.setText(R.string.cancel);
110 | } else if (displayedChild == mViewPages.getChildCount() - 1) {
111 | mNextButton.setText(R.string.finish);
112 | } else {
113 | mPreviousButton.setText(R.string.previous);
114 | mNextButton.setText(R.string.next);
115 | }
116 | }
117 |
118 | @Override
119 | public void onReachedEnd() {
120 | getActivity().finish();
121 | }
122 |
123 | @Override
124 | public void onReachedStart() {
125 | getActivity().finish();
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/fragment/RememberMasterPasswordPatternFragment.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.fragment;
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 |
10 | import org.mariotaku.pass.R;
11 | import org.mariotaku.pass.view.RememberMasterPasswordContainer;
12 |
13 | /**
14 | * Created by mariotaku on 15/10/31.
15 | */
16 | public class RememberMasterPasswordPatternFragment extends Fragment implements View.OnClickListener, RememberMasterPasswordContainer.PageListener {
17 | private RememberMasterPasswordContainer mViewPages;
18 | private Button mPreviousButton, mNextButton;
19 |
20 | @Override
21 | public void onActivityCreated(final Bundle savedInstanceState) {
22 | super.onActivityCreated(savedInstanceState);
23 | mNextButton.setOnClickListener(this);
24 | mPreviousButton.setOnClickListener(this);
25 | mViewPages.setPageListener(this);
26 |
27 | updatePage();
28 | }
29 |
30 | @Override
31 | public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
32 | return inflater.inflate(R.layout.fragment_remember_master_password_pattern, container, false);
33 | }
34 |
35 | @Override
36 | public void onViewCreated(final View view, final Bundle savedInstanceState) {
37 | super.onViewCreated(view, savedInstanceState);
38 | mViewPages = ((RememberMasterPasswordContainer) view.findViewById(R.id.view_pages));
39 | mPreviousButton = (Button) view.findViewById(R.id.previous);
40 | mNextButton = (Button) view.findViewById(R.id.next);
41 | }
42 |
43 | @Override
44 | public void onClick(final View v) {
45 | switch (v.getId()) {
46 | case R.id.next: {
47 | mViewPages.showNext();
48 | break;
49 | }
50 | case R.id.previous: {
51 | mViewPages.showPrevious();
52 | break;
53 | }
54 | }
55 | }
56 |
57 | @Override
58 | public void onPageChanged(final int current) {
59 | updatePage();
60 | }
61 |
62 | private void updatePage() {
63 | final int displayedChild = mViewPages.getDisplayedChild();
64 | if (displayedChild == 0) {
65 | mPreviousButton.setText(R.string.cancel);
66 | } else if (displayedChild == mViewPages.getChildCount() - 1) {
67 | mNextButton.setText(R.string.finish);
68 | } else {
69 | mPreviousButton.setText(R.string.previous);
70 | mNextButton.setText(R.string.next);
71 | }
72 | }
73 |
74 | @Override
75 | public void onReachedEnd() {
76 | getActivity().finish();
77 | }
78 |
79 | @Override
80 | public void onReachedStart() {
81 | getActivity().finish();
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/fragment/SettingsDetailsFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Twidere - Twitter client for Android
3 | *
4 | * Copyright (C) 2012-2014 Mariotaku Lee
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package org.mariotaku.pass.fragment;
21 |
22 | import android.content.SharedPreferences;
23 | import android.os.Bundle;
24 | import android.preference.Preference;
25 | import android.preference.PreferenceFragment;
26 | import android.preference.PreferenceManager;
27 | import android.preference.PreferenceScreen;
28 |
29 | import org.mariotaku.pass.Constants;
30 | import org.mariotaku.pass.util.Utils;
31 |
32 |
33 | public class SettingsDetailsFragment extends PreferenceFragment implements Constants, SharedPreferences.OnSharedPreferenceChangeListener {
34 |
35 | @Override
36 | public void onActivityCreated(final Bundle savedInstanceState) {
37 | super.onActivityCreated(savedInstanceState);
38 | final PreferenceManager preferenceManager = getPreferenceManager();
39 | preferenceManager.setSharedPreferencesName(SHARED_PREFERENCES_NAME);
40 | final PreferenceScreen defaultScreen = getPreferenceScreen();
41 | final PreferenceScreen preferenceScreen;
42 | if (defaultScreen != null) {
43 | defaultScreen.removeAll();
44 | preferenceScreen = defaultScreen;
45 | } else {
46 | preferenceScreen = preferenceManager.createPreferenceScreen(getActivity());
47 | }
48 | setPreferenceScreen(preferenceScreen);
49 | final Bundle args = getArguments();
50 | final Object rawResId = args.get(EXTRA_RESID);
51 | final int resId;
52 | if (rawResId instanceof Integer) {
53 | resId = (Integer) rawResId;
54 | } else if (rawResId instanceof String) {
55 | resId = Utils.getResId(getActivity(), (String) rawResId);
56 | } else {
57 | resId = 0;
58 | }
59 | if (resId != 0) {
60 | addPreferencesFromResource(resId);
61 | }
62 | }
63 |
64 | @Override
65 | public void onStart() {
66 | super.onStart();
67 | final SharedPreferences preferences = getPreferenceManager().getSharedPreferences();
68 | preferences.registerOnSharedPreferenceChangeListener(this);
69 | }
70 |
71 | @Override
72 | public void onStop() {
73 | final SharedPreferences preferences = getPreferenceManager().getSharedPreferences();
74 | preferences.unregisterOnSharedPreferenceChangeListener(this);
75 | super.onStop();
76 | }
77 |
78 | @Override
79 | public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
80 | final Preference preference = findPreference(key);
81 | if (preference == null) return;
82 | final Bundle extras = preference.getExtras();
83 | // if (extras != null && extras.containsKey(EXTRA_NOTIFY_CHANGE)) {
84 | // SettingsActivity.setShouldNotifyChange(getActivity());
85 | // }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/model/AccessibilityExtra.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.model;
2 |
3 | import android.os.Parcel;
4 | import android.os.Parcelable;
5 | import android.support.annotation.NonNull;
6 | import android.view.accessibility.AccessibilityEvent;
7 |
8 | /**
9 | * Created by mariotaku on 15/11/18.
10 | */
11 | public class AccessibilityExtra implements Parcelable {
12 |
13 | @NonNull
14 | public AccessibilityEvent window, view;
15 |
16 | public AccessibilityExtra(@NonNull final AccessibilityEvent window, @NonNull final AccessibilityEvent view) {
17 | this.window = window;
18 | this.view = view;
19 | }
20 |
21 | protected AccessibilityExtra(Parcel in) {
22 | window = in.readParcelable(AccessibilityEvent.class.getClassLoader());
23 | view = in.readParcelable(AccessibilityEvent.class.getClassLoader());
24 | }
25 |
26 | @Override
27 | public void writeToParcel(Parcel dest, int flags) {
28 | dest.writeParcelable(window, flags);
29 | dest.writeParcelable(view, flags);
30 | }
31 |
32 | @Override
33 | public int describeContents() {
34 | return 0;
35 | }
36 |
37 | public static final Creator CREATOR = new Creator() {
38 | @Override
39 | public AccessibilityExtra createFromParcel(Parcel in) {
40 | return new AccessibilityExtra(in);
41 | }
42 |
43 | @Override
44 | public AccessibilityExtra[] newArray(int size) {
45 | return new AccessibilityExtra[size];
46 | }
47 | };
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/preference/NumberPickerPreference.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.preference;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.preference.DialogPreference;
6 | import android.util.AttributeSet;
7 | import android.view.View;
8 | import android.widget.NumberPicker;
9 |
10 | import org.mariotaku.pass.R;
11 |
12 | public class NumberPickerPreference extends DialogPreference {
13 | private final int mMinValue, mMaxValue;
14 | private NumberPicker mPicker;
15 | private Integer mNumber = 0;
16 |
17 | public NumberPickerPreference(Context context, AttributeSet attrs) {
18 | this(context, attrs, android.R.attr.dialogPreferenceStyle);
19 | }
20 |
21 | public NumberPickerPreference(Context context, AttributeSet attrs, int defStyle) {
22 | super(context, attrs, defStyle);
23 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NumberPickerPreferences);
24 | mMinValue = a.getInt(R.styleable.NumberPickerPreferences_min, 0);
25 | mMaxValue = a.getInt(R.styleable.NumberPickerPreferences_max, 10);
26 | a.recycle();
27 | setPositiveButtonText(android.R.string.ok);
28 | setNegativeButtonText(android.R.string.cancel);
29 | }
30 |
31 | @Override
32 | protected View onCreateDialogView() {
33 | mPicker = new NumberPicker(getContext());
34 | mPicker.setMinValue(mMinValue);
35 | mPicker.setMaxValue(mMaxValue);
36 | mPicker.setValue(mNumber);
37 | return mPicker;
38 | }
39 |
40 | @Override
41 | protected void onDialogClosed(boolean positiveResult) {
42 | if (positiveResult) {
43 | // needed when user edits the text field and clicks OK
44 | mPicker.clearFocus();
45 | setValue(mPicker.getValue());
46 | }
47 | }
48 |
49 | @Override
50 | protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
51 | setValue(restoreValue ? getPersistedInt(mNumber) : (Integer) defaultValue);
52 | }
53 |
54 | public void setValue(int value) {
55 | if (shouldPersist()) {
56 | persistInt(value);
57 | }
58 |
59 | if (value != mNumber) {
60 | mNumber = value;
61 | notifyChanged();
62 | }
63 | }
64 |
65 | @Override
66 | protected Object onGetDefaultValue(TypedArray a, int index) {
67 | return a.getInt(index, 0);
68 | }
69 | }
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/provider/PassProvider.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.provider;
2 |
3 | import android.content.ContentProvider;
4 | import android.content.ContentResolver;
5 | import android.content.ContentValues;
6 | import android.content.Context;
7 | import android.content.UriMatcher;
8 | import android.database.Cursor;
9 | import android.database.sqlite.SQLiteDatabase;
10 | import android.net.Uri;
11 | import android.provider.BaseColumns;
12 | import android.support.annotation.NonNull;
13 | import android.support.annotation.Nullable;
14 |
15 | import com.readystatesoftware.sqliteasset.SQLiteAssetHelper;
16 |
17 | import org.mariotaku.pass.BuildConfig;
18 | import org.mariotaku.sqliteqb.library.Columns;
19 | import org.mariotaku.sqliteqb.library.Constraint;
20 | import org.mariotaku.sqliteqb.library.DataType;
21 | import org.mariotaku.sqliteqb.library.NewColumn;
22 | import org.mariotaku.sqliteqb.library.OnConflict;
23 | import org.mariotaku.sqliteqb.library.SQLQueryBuilder;
24 |
25 | /**
26 | * Created by mariotaku on 15/11/17.
27 | */
28 | public class PassProvider extends ContentProvider {
29 |
30 | public static final Uri BASE_CONTENT_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
31 | .authority(BuildConfig.APPLICATION_ID).build();
32 | public static final int TABLE_ID_RECENT_DOMAINS = 1;
33 | public static final int TABLE_ID_NFC_TAGS = 2;
34 | public static final int TABLE_ID_APP_WINDOWS = 3;
35 | private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
36 |
37 | static {
38 | URI_MATCHER.addURI(BuildConfig.APPLICATION_ID, "/recent_domains", TABLE_ID_RECENT_DOMAINS);
39 | URI_MATCHER.addURI(BuildConfig.APPLICATION_ID, "/nfc_tags", TABLE_ID_NFC_TAGS);
40 | URI_MATCHER.addURI(BuildConfig.APPLICATION_ID, "/app_windows", TABLE_ID_APP_WINDOWS);
41 | }
42 |
43 | private SQLiteDatabase mDatabase;
44 |
45 | @Override
46 | public boolean onCreate() {
47 | mDatabase = new SQLiteAssetHelper(getContext(), "pass.db", null, 1).getWritableDatabase();
48 | return false;
49 | }
50 |
51 | @Nullable
52 | @Override
53 | public Cursor query(@NonNull final Uri uri, final String[] projection, final String selection, final String[] selectionArgs, final String sortOrder) {
54 | final Cursor cursor = mDatabase.query(getTable(uri), projection, selection, selectionArgs, null, null, sortOrder);
55 | setNotificationUri(cursor, uri);
56 | return cursor;
57 | }
58 |
59 | @Nullable
60 | @Override
61 | public String getType(@NonNull final Uri uri) {
62 | return null;
63 | }
64 |
65 | @Nullable
66 | @Override
67 | public Uri insert(@NonNull final Uri uri, final ContentValues values) {
68 | final long insertedId = mDatabase.insert(getTable(uri), null, values);
69 | notifyChange(uri);
70 | return Uri.withAppendedPath(uri, String.valueOf(insertedId));
71 | }
72 |
73 | @Override
74 | public int delete(@NonNull final Uri uri, final String selection, final String[] selectionArgs) {
75 | final int rowsAffected = mDatabase.delete(getTable(uri), selection, selectionArgs);
76 | if (rowsAffected > 0) {
77 | notifyChange(uri);
78 | }
79 | return rowsAffected;
80 | }
81 |
82 | @Override
83 | public int update(@NonNull final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) {
84 | final int rowsAffected = mDatabase.update(getTable(uri), values, selection, selectionArgs);
85 | if (rowsAffected > 0) {
86 | notifyChange(uri);
87 | }
88 | return rowsAffected;
89 | }
90 |
91 | private void setNotificationUri(final Cursor cursor, final Uri uri) {
92 | final Context context = getContext();
93 | assert context != null;
94 | cursor.setNotificationUri(context.getContentResolver(), uri);
95 | }
96 |
97 | private String getTable(final Uri uri) {
98 | switch (URI_MATCHER.match(uri)) {
99 | case TABLE_ID_RECENT_DOMAINS: {
100 | return PassDataStore.RecentDomains.TABLE_NAME;
101 | }
102 | case TABLE_ID_NFC_TAGS: {
103 | return PassDataStore.NfcTags.TABLE_NAME;
104 | }
105 | case TABLE_ID_APP_WINDOWS: {
106 | return PassDataStore.AppWindows.TABLE_NAME;
107 | }
108 | }
109 | return null;
110 | }
111 |
112 | private void notifyChange(final Uri uri) {
113 | final Context context = getContext();
114 | assert context != null;
115 | context.getContentResolver().notifyChange(uri, null);
116 | }
117 |
118 |
119 | public interface PassDataStore {
120 | interface RecentDomains extends BaseColumns {
121 |
122 | String TABLE_NAME = "recent_domains";
123 | String DOMAIN = "domain";
124 | String RECENT = "recent";
125 | String[] COLUMNS = {_ID, DOMAIN, RECENT};
126 | Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, "recent_domains");
127 | }
128 |
129 | interface NfcTags extends BaseColumns {
130 | String TABLE_NAME = "nfc_tags";
131 | String TAG_ID = "tag_id";
132 | String DOMAIN = "domain";
133 | String NAME = "name";
134 | String[] COLUMNS = {_ID, TAG_ID, DOMAIN, NAME};
135 | Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, "nfc_tags");
136 | }
137 |
138 | interface AppWindows extends BaseColumns {
139 | String TABLE_NAME = "app_windows";
140 | String PACKAGE_NAME = "package_name";
141 | String WINDOW_NAME = "window_name";
142 | String DOMAIN = "domain";
143 | String[] COLUMNS = {_ID, PACKAGE_NAME, WINDOW_NAME, DOMAIN};
144 | Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, "app_windows");
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/service/ClipboardRestoreService.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.service;
2 |
3 | import android.app.IntentService;
4 | import android.app.Notification;
5 | import android.app.NotificationManager;
6 | import android.content.ClipData;
7 | import android.content.ClipboardManager;
8 | import android.content.Context;
9 | import android.content.Intent;
10 | import android.content.SharedPreferences;
11 | import android.os.SystemClock;
12 |
13 | import org.mariotaku.pass.Constants;
14 | import org.mariotaku.pass.R;
15 |
16 |
17 | public class ClipboardRestoreService extends IntentService implements Constants {
18 |
19 | public static void startCopy(Context context, ClipData clipData) {
20 | Intent intent = new Intent(context, ClipboardRestoreService.class);
21 | intent.setClipData(clipData);
22 | context.startService(intent);
23 | }
24 |
25 | public ClipboardRestoreService() {
26 | super("ClipboardRestoreService");
27 | }
28 |
29 | @Override
30 | protected void onHandleIntent(Intent intent) {
31 | if (intent == null) return;
32 | final ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
33 | final NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
34 | final SharedPreferences pref = getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE);
35 | if (!pref.getBoolean(KEY_CLEAR_COPIED_PASSWORD_AUTOMATICALLY, true)) {
36 | cm.setPrimaryClip(intent.getClipData());
37 | return;
38 | }
39 | final ClipboardChangedListener listener = new ClipboardChangedListener(cm);
40 | // Get primary clip as backup
41 | listener.current = cm.getPrimaryClip();
42 |
43 | final ClipData newData = intent.getClipData();
44 | cm.setPrimaryClip(newData);
45 | cm.addPrimaryClipChangedListener(listener);
46 | // Wait for time up
47 | final Notification.Builder nb = new Notification.Builder(this);
48 | nb.setSmallIcon(R.drawable.ic_stat_password);
49 | nb.setContentTitle(getString(R.string.app_name));
50 | nb.setContentText(getString(R.string.password_copied));
51 | nb.setOngoing(true);
52 | final int duration = Integer.parseInt(pref.getString(KEY_COPIED_PASSWORD_VALIDITY, "15000"));
53 | long start = SystemClock.uptimeMillis(), end = start + duration, curr = start;
54 | while (curr < end) {
55 | final int progress = (int) ((end - curr) / (double) duration * 100);
56 | try {
57 | Thread.sleep(100);
58 | } catch (InterruptedException e) {
59 | // Ignore
60 | }
61 | nb.setProgress(100, progress, false);
62 | nm.notify(NOTIFICATION_ID_CLIPBOARD, nb.build());
63 | curr = SystemClock.uptimeMillis();
64 | }
65 | // Clipboard may change during password waiting time, now restore them
66 | if (listener.current != null) {
67 | cm.setPrimaryClip(listener.current);
68 | } else {
69 | cm.setPrimaryClip(ClipData.newPlainText("", ""));
70 | }
71 | cm.removePrimaryClipChangedListener(listener);
72 | nm.cancel(NOTIFICATION_ID_CLIPBOARD);
73 | }
74 |
75 | private static class ClipboardChangedListener implements ClipboardManager.OnPrimaryClipChangedListener {
76 |
77 | private final ClipboardManager cm;
78 | private ClipData current;
79 |
80 | ClipboardChangedListener(ClipboardManager cm) {
81 | this.cm = cm;
82 | }
83 |
84 | @Override
85 | public void onPrimaryClipChanged() {
86 | current = cm.getPrimaryClip();
87 | }
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/service/PasswordHelperService.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.service;
2 |
3 | import android.accessibilityservice.AccessibilityService;
4 | import android.app.Notification;
5 | import android.app.NotificationManager;
6 | import android.app.PendingIntent;
7 | import android.content.BroadcastReceiver;
8 | import android.content.ClipData;
9 | import android.content.ClipboardManager;
10 | import android.content.Context;
11 | import android.content.Intent;
12 | import android.content.IntentFilter;
13 | import android.os.Bundle;
14 | import android.support.annotation.NonNull;
15 | import android.support.annotation.Nullable;
16 | import android.support.v4.content.LocalBroadcastManager;
17 | import android.text.TextUtils;
18 | import android.util.Log;
19 | import android.util.Patterns;
20 | import android.view.accessibility.AccessibilityEvent;
21 | import android.view.accessibility.AccessibilityNodeInfo;
22 | import android.webkit.WebView;
23 | import android.widget.EditText;
24 | import android.widget.Toast;
25 |
26 | import org.mariotaku.pass.BuildConfig;
27 | import org.mariotaku.pass.Constants;
28 | import org.mariotaku.pass.R;
29 | import org.mariotaku.pass.activity.PassGenDialogActivity;
30 | import org.mariotaku.pass.model.AccessibilityExtra;
31 |
32 | import java.util.ArrayList;
33 | import java.util.List;
34 | import java.util.concurrent.atomic.AtomicReference;
35 |
36 | /**
37 | * Created by mariotaku on 15/11/18.
38 | */
39 | public class PasswordHelperService extends AccessibilityService implements Constants {
40 |
41 | private BroadcastReceiver mCallbackReceiver = new BroadcastReceiver() {
42 | @Override
43 | public void onReceive(final Context context, final Intent intent) {
44 | final AccessibilityExtra extra = intent.getParcelableExtra(EXTRA_ACCESSIBILITY_EXTRA);
45 | final CharSequence text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
46 | if (extra == null || text == null) return;
47 | mCallbackData.set(new PasswordCallbackData(extra, text));
48 |
49 | }
50 | };
51 |
52 | private final AtomicReference mCurrentWindow = new AtomicReference<>();
53 | private final AtomicReference mCurrentView = new AtomicReference<>();
54 |
55 | private final AtomicReference mPasswordWindow = new AtomicReference<>();
56 | private final AtomicReference mPasswordView = new AtomicReference<>();
57 |
58 | private final AtomicReference mCallbackData = new AtomicReference<>();
59 |
60 | private AtomicReference mCurrentViewingLink = new AtomicReference<>();
61 |
62 |
63 | @Override
64 | public void onCreate() {
65 | super.onCreate();
66 | LocalBroadcastManager.getInstance(this).registerReceiver(mCallbackReceiver, new IntentFilter(ACTION_PASSWORD_CALLBACK));
67 | }
68 |
69 | @Override
70 | public void onDestroy() {
71 | LocalBroadcastManager.getInstance(this).unregisterReceiver(mCallbackReceiver);
72 | super.onDestroy();
73 | }
74 |
75 | @Override
76 | public void onInterrupt() {
77 | }
78 |
79 | @Override
80 | public void onAccessibilityEvent(final AccessibilityEvent event) {
81 | // Ignore changes inside myself
82 | if (TextUtils.equals(getPackageName(), event.getPackageName())) return;
83 | switch (event.getEventType()) {
84 | case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
85 | // Not an activity window, skip
86 | if (event.getCurrentItemIndex() >= 0 || event.getItemCount() >= 0) {
87 | if (BuildConfig.DEBUG) {
88 | Log.d(LOGTAG, "Ignoring window state " + event);
89 | }
90 | break;
91 | }
92 | final AccessibilityEvent passwordWindowEvent = mPasswordWindow.get();
93 | final AccessibilityEvent passwordViewEvent = mPasswordView.get();
94 | // Went back to password input window
95 | if (passwordWindowEvent != null && passwordViewEvent != null && isSameWindow(event, passwordWindowEvent)) {
96 | final AccessibilityNodeInfo inputField = findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
97 | final PasswordCallbackData data = mCallbackData.getAndSet(null);
98 | // Current focusing view is password view
99 | if (isSameView(inputField, passwordViewEvent.getSource())) {
100 | // Not finishing from PassGen, just switch back
101 | if (data == null) {
102 | onFocusedToPasswordView(passwordViewEvent, passwordWindowEvent, mCurrentViewingLink.get());
103 | } else if (pasteText(inputField, data.password)) {
104 | // Do something after copied successfully
105 | } else {
106 | Toast.makeText(this, R.string.past_manually_hint, Toast.LENGTH_SHORT).show();
107 | }
108 | } else {
109 | // Clear notification
110 | final NotificationManager nm = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
111 | nm.cancel(NOTIFICATION_ID_ACCESSIBILITY);
112 | }
113 | } else {
114 | // Clear notification
115 | final NotificationManager nm = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
116 | nm.cancel(NOTIFICATION_ID_ACCESSIBILITY);
117 | }
118 | final AccessibilityNodeInfo webViewNode = findWebViewNode(getRootInActiveWindow());
119 | if (webViewNode != null) {
120 | handleBrowserLink(event, webViewNode);
121 | }
122 | mCurrentWindow.set(AccessibilityEvent.obtain(event));
123 | mCurrentViewingLink.set(null);
124 | break;
125 | }
126 | case AccessibilityEvent.TYPE_VIEW_FOCUSED: {
127 | // Handle password fields
128 | mCurrentView.getAndSet(AccessibilityEvent.obtain(event));
129 | final AccessibilityEvent windowEvent = mCurrentWindow.get();
130 | AccessibilityEvent passwordView = mPasswordView.get();
131 | if (event.isPassword() && windowEvent != null
132 | && TextUtils.equals(event.getPackageName(), windowEvent.getPackageName())) {
133 | final BrowserState browserState = mCurrentViewingLink.get();
134 | if (browserState != null && TextUtils.equals(event.getPackageName(), browserState.packageName)) {
135 | onFocusedToPasswordView(event, windowEvent, browserState);
136 | } else {
137 | onFocusedToPasswordView(event, windowEvent, null);
138 | }
139 | } else if (passwordView == null || !isSameView(passwordView.getSource(), event.getSource())) {
140 | // Clear notification
141 | final NotificationManager nm = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
142 | nm.cancel(NOTIFICATION_ID_ACCESSIBILITY);
143 | }
144 | break;
145 | }
146 | case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: {
147 | final AccessibilityNodeInfo source = event.getSource();
148 | if (source != null && TextUtils.equals(source.getClassName(), WebView.class.getName())) {
149 | handleBrowserLink(event, source);
150 | }
151 | break;
152 | }
153 | }
154 | }
155 |
156 | @Nullable
157 | private AccessibilityNodeInfo findWebViewNode(@Nullable final AccessibilityNodeInfo node) {
158 | if (node == null) return null;
159 | if (TextUtils.equals(node.getClassName(), WebView.class.getName())) return node;
160 | AccessibilityNodeInfo webViewNode = null;
161 | for (int i = 0, j = node.getChildCount(); i < j; i++) {
162 | webViewNode = findWebViewNode(node.getChild(i));
163 | if (webViewNode != null) break;
164 | }
165 | return webViewNode;
166 | }
167 |
168 | private void handleBrowserLink(final AccessibilityEvent event, final AccessibilityNodeInfo source) {
169 | final List editTextValues = new ArrayList<>();
170 | findEditTextValues(getRootInActiveWindow(), source, editTextValues);
171 | CharSequence desiredUri = null;
172 | for (final CharSequence value : editTextValues) {
173 | if (TextUtils.isEmpty(value)) continue;
174 | if (Patterns.WEB_URL.matcher(value).matches()) {
175 | desiredUri = value;
176 | break;
177 | }
178 | }
179 | mCurrentViewingLink.set(new BrowserState(event.getPackageName(), desiredUri));
180 | }
181 |
182 | private void onFocusedToPasswordView(final AccessibilityEvent viewEvent,
183 | final AccessibilityEvent windowEvent,
184 | final BrowserState browserState) {
185 | mPasswordWindow.set(AccessibilityEvent.obtain(windowEvent));
186 | mPasswordView.set(AccessibilityEvent.obtain(viewEvent));
187 |
188 | final NotificationManager nm = ((NotificationManager) getSystemService(NOTIFICATION_SERVICE));
189 | final Notification.Builder builder = new Notification.Builder(this);
190 | builder.setSmallIcon(R.drawable.ic_stat_password);
191 | builder.setPriority(Notification.PRIORITY_MAX);
192 | builder.setVibrate(new long[0]);
193 | builder.setAutoCancel(true);
194 | builder.setContentTitle(getString(R.string.app_name));
195 | builder.setContentText(getString(R.string.accessibility_password_hint));
196 | final Intent passGenIntent = new Intent(this, PassGenDialogActivity.class);
197 | passGenIntent.putExtra(EXTRA_ACCESSIBILITY_EXTRA, new AccessibilityExtra(windowEvent, viewEvent));
198 | if (browserState != null && !TextUtils.isEmpty(browserState.link)) {
199 | passGenIntent.putExtra(Intent.EXTRA_TEXT, browserState.link);
200 | }
201 | builder.setContentIntent(PendingIntent.getActivity(this, 0, passGenIntent, PendingIntent.FLAG_UPDATE_CURRENT));
202 | //noinspection deprecation
203 | //builder.addAction(new Notification.Action(R.drawable.ic_action_clear, "Never for this screen", null));
204 | nm.notify(NOTIFICATION_ID_ACCESSIBILITY, builder.build());
205 | }
206 |
207 | private boolean pasteText(AccessibilityNodeInfo source, CharSequence text) {
208 | if (source.getActionList().contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT)) {
209 | final Bundle arguments = new Bundle();
210 | arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
211 | return source.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
212 | } else {
213 | final ClipboardManager cm = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
214 | final ClipData clipBackup = cm.getPrimaryClip();
215 | cm.setPrimaryClip(ClipData.newPlainText(getString(R.string.copied_password), text));
216 | final boolean result = source.performAction(AccessibilityNodeInfo.ACTION_PASTE);
217 | if (clipBackup != null) {
218 | cm.setPrimaryClip(clipBackup);
219 | } else {
220 | cm.setPrimaryClip(ClipData.newPlainText("", ""));
221 | }
222 | return result;
223 | }
224 | }
225 |
226 | private void findEditTextValues(@Nullable final AccessibilityNodeInfo root, final AccessibilityNodeInfo exclude, List out) {
227 | if (root == null) return;
228 | if (TextUtils.equals(root.getClassName(), EditText.class.getName())) {
229 | out.add(root.getText());
230 | }
231 | for (int i = 0, j = root.getChildCount(); i < j; i++) {
232 | final AccessibilityNodeInfo info = root.getChild(i);
233 | if (info != null && !info.equals(exclude)) {
234 | findEditTextValues(info, exclude, out);
235 | }
236 | }
237 | }
238 |
239 | private boolean isSameView(final AccessibilityNodeInfo left, final AccessibilityNodeInfo right) {
240 | if (left == null || right == null) return false;
241 | return left.equals(right);
242 | }
243 |
244 | private boolean isSameWindow(final AccessibilityEvent left, final AccessibilityEvent right) {
245 | if (left == null || right == null) return false;
246 | return TextUtils.equals(left.getClassName(), right.getClassName())
247 | && TextUtils.equals(left.getPackageName(), right.getPackageName());
248 | }
249 |
250 | static class PasswordCallbackData {
251 | @NonNull
252 | AccessibilityExtra extra;
253 | @NonNull
254 | CharSequence password;
255 |
256 | public PasswordCallbackData(@NonNull final AccessibilityExtra extra, @NonNull final CharSequence password) {
257 | this.extra = extra;
258 | this.password = password;
259 | }
260 | }
261 |
262 | static class BrowserState {
263 | @NonNull
264 | CharSequence packageName;
265 | @Nullable
266 | CharSequence link;
267 |
268 | public BrowserState(@NonNull final CharSequence packageName, @Nullable final CharSequence link) {
269 | this.packageName = packageName;
270 | this.link = link;
271 | }
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/service/QuickSettingsTileService.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.service;
2 |
3 | import android.annotation.TargetApi;
4 | import android.content.Intent;
5 | import android.os.Build;
6 | import android.service.quicksettings.TileService;
7 |
8 | import org.mariotaku.pass.activity.PassGenDialogActivity;
9 |
10 | /**
11 | * Created by mariotaku on 16/9/1.
12 | */
13 | @TargetApi(Build.VERSION_CODES.N)
14 | public class QuickSettingsTileService extends TileService {
15 | @Override
16 | public void onClick() {
17 | unlockAndRun(new Runnable() {
18 | @Override
19 | public void run() {
20 | Intent intent = new Intent(QuickSettingsTileService.this, PassGenDialogActivity.class);
21 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
22 | startActivity(intent);
23 | sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
24 | }
25 | });
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/util/ContextCompat.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.util;
2 |
3 | /**
4 | * Created by mariotaku on 15/11/2.
5 | */
6 | public class ContextCompat {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/util/FingerprintCryptoHelper.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.util;
2 |
3 | import android.annotation.TargetApi;
4 | import android.os.Build;
5 | import android.security.keystore.KeyGenParameterSpec;
6 | import android.security.keystore.KeyProperties;
7 |
8 | import org.mariotaku.pass.BuildConfig;
9 |
10 | import java.security.KeyStore;
11 | import java.security.KeyStoreException;
12 | import java.security.NoSuchAlgorithmException;
13 | import java.security.NoSuchProviderException;
14 |
15 | import javax.crypto.Cipher;
16 | import javax.crypto.KeyGenerator;
17 | import javax.crypto.NoSuchPaddingException;
18 |
19 | /**
20 | * Created by mariotaku on 15/11/2.
21 | */
22 | @TargetApi(Build.VERSION_CODES.M)
23 | public class FingerprintCryptoHelper {
24 |
25 | public static final String KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
26 | public static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;
27 | public static final String ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7;
28 |
29 | public static final String SECURITY_KEY_NAME = BuildConfig.APPLICATION_ID + ".security_key";
30 |
31 | public static KeyStore getKeystore() {
32 | try {
33 | return KeyStore.getInstance("AndroidKeyStore");
34 | } catch (KeyStoreException e) {
35 | throw new RuntimeException(e);
36 | }
37 | }
38 |
39 | public static Cipher getCipher() {
40 | try {
41 | return Cipher.getInstance(KEY_ALGORITHM + "/" + BLOCK_MODE + "/" + ENCRYPTION_PADDING);
42 | } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
43 | throw new RuntimeException(e);
44 | }
45 | }
46 |
47 | public static KeyGenerator getKeyGenerator() {
48 | try {
49 | return KeyGenerator.getInstance(KEY_ALGORITHM, "AndroidKeyStore");
50 | } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
51 | throw new RuntimeException(e);
52 | }
53 | }
54 |
55 | public static KeyGenParameterSpec createKeyGenParameterSpec() {
56 | return new KeyGenParameterSpec.Builder(SECURITY_KEY_NAME,
57 | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
58 | .setBlockModes(BLOCK_MODE)
59 | // Require the user to authenticate with a fingerprint to authorize every use
60 | // of the key
61 | .setUserAuthenticationRequired(true)
62 | .setEncryptionPaddings(ENCRYPTION_PADDING)
63 | .build();
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/util/FingerprintHelper.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.util;
2 |
3 | import android.Manifest;
4 | import android.annotation.TargetApi;
5 | import android.app.Activity;
6 | import android.app.Fragment;
7 | import android.content.Context;
8 | import android.content.pm.PackageManager;
9 | import android.os.Build;
10 | import android.os.Handler;
11 | import android.support.annotation.NonNull;
12 | import android.support.annotation.Nullable;
13 | import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
14 | import android.support.v4.os.CancellationSignal;
15 |
16 | /**
17 | * Created by mariotaku on 15/11/1.
18 | */
19 | public abstract class FingerprintHelper {
20 |
21 | final Context context;
22 | final FingerprintManagerCompat delegate;
23 |
24 | public FingerprintHelper(final Context context) {
25 | this.context = context;
26 | this.delegate = FingerprintManagerCompat.from(context);
27 | }
28 |
29 | public static FingerprintHelper getInstance(Activity activity) {
30 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return new NoOp(activity);
31 | return new ImplM(activity);
32 | }
33 |
34 | public void authenticate(@Nullable final FingerprintManagerCompat.CryptoObject crypto, final int flags,
35 | @Nullable final CancellationSignal cancel, @NonNull final FingerprintManagerCompat.AuthenticationCallback callback, @Nullable final Handler handler) throws SecurityException {
36 | delegate.authenticate(crypto, flags, cancel, callback, handler);
37 | }
38 |
39 | public boolean isHardwareDetected() throws SecurityException {
40 | return delegate.isHardwareDetected();
41 | }
42 |
43 | public boolean hasEnrolledFingerprints() throws SecurityException {
44 | return delegate.hasEnrolledFingerprints();
45 | }
46 |
47 | public abstract boolean requestPermission(Activity activity, final int requestCode);
48 |
49 | public abstract boolean requestPermission(Fragment fragment, final int requestCode);
50 |
51 | public abstract boolean hasPermission();
52 |
53 |
54 | @TargetApi(Build.VERSION_CODES.M)
55 | private static class ImplM extends FingerprintHelper {
56 |
57 | public ImplM(final Activity activity) {
58 | super(activity);
59 | }
60 |
61 | @Override
62 | public boolean requestPermission(Activity activity, final int requestCode) {
63 | if (hasPermission()) return true;
64 | activity.requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, requestCode);
65 | return false;
66 | }
67 |
68 | @Override
69 | public boolean requestPermission(final Fragment fragment, final int requestCode) {
70 | if (hasPermission()) return true;
71 | fragment.requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT}, requestCode);
72 | return false;
73 | }
74 |
75 | @Override
76 | public boolean hasPermission() {
77 | return context.checkSelfPermission(Manifest.permission.USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED;
78 | }
79 | }
80 |
81 | private static class NoOp extends FingerprintHelper {
82 | public NoOp(final Activity activity) {
83 | super(activity);
84 | }
85 |
86 | @Override
87 | public boolean requestPermission(Activity activity, final int requestCode) {
88 | return true;
89 | }
90 |
91 | @Override
92 | public boolean requestPermission(final Fragment fragment, final int requestCode) {
93 | return true;
94 | }
95 |
96 | @Override
97 | public boolean hasPermission() {
98 | return false;
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/util/MasterPasswordEncrypter.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.util;
2 |
3 | import android.content.Context;
4 | import android.util.Pair;
5 |
6 | import java.security.GeneralSecurityException;
7 | import java.security.NoSuchAlgorithmException;
8 | import java.security.NoSuchProviderException;
9 | import java.security.SecureRandom;
10 |
11 | import javax.crypto.BadPaddingException;
12 | import javax.crypto.Cipher;
13 | import javax.crypto.KeyGenerator;
14 | import javax.crypto.SecretKey;
15 | import javax.crypto.spec.IvParameterSpec;
16 | import javax.crypto.spec.SecretKeySpec;
17 |
18 | /**
19 | * Created by mariotaku on 15/10/30.
20 | */
21 | public class MasterPasswordEncrypter {
22 |
23 | public MasterPasswordEncrypter() {
24 | }
25 |
26 |
27 | public byte[] decrypt(byte[] in, byte[] key, byte[] iv) throws InternalErrorException, WrongPasswordException {
28 | try {
29 | final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
30 | cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(getRawKey(key), "AES"), new IvParameterSpec(iv));
31 | return cipher.doFinal(in);
32 | } catch (BadPaddingException e) {
33 | throw new WrongPasswordException(e);
34 | } catch (GeneralSecurityException e) {
35 | throw new InternalErrorException(e);
36 | }
37 | }
38 |
39 | public Pair encrypt(byte[] in, byte[] key) throws InternalErrorException {
40 | try {
41 | final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
42 | cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(getRawKey(key), "AES"));
43 | return Pair.create(cipher.doFinal(in), cipher.getIV());
44 | } catch (GeneralSecurityException e) {
45 | throw new InternalErrorException(e);
46 | }
47 | }
48 |
49 |
50 | /**
51 | * Get 256-bit encryption key
52 | *
53 | * From http://blog.csdn.net/zhaokaiqiang1992/article/details/41142883
54 | *
55 | * @param seed
56 | * @return
57 | * @throws Exception
58 | */
59 | private static byte[] getRawKey(byte[] seed) throws NoSuchAlgorithmException, NoSuchProviderException {
60 | final KeyGenerator kgen = KeyGenerator.getInstance("AES");
61 | final SecureRandom sr = SecureRandom.getInstance("SHA1PRNG", "Crypto");
62 | sr.setSeed(seed);
63 | // 256 bits or 128 bits,192bits
64 | kgen.init(256, sr);
65 | final SecretKey key = kgen.generateKey();
66 | return key.getEncoded();
67 | }
68 |
69 | public static class InternalErrorException extends GeneralSecurityException {
70 |
71 | public InternalErrorException(final Exception e) {
72 | super(e);
73 | }
74 | }
75 |
76 | public static class WrongPasswordException extends GeneralSecurityException {
77 |
78 | public WrongPasswordException(final Exception e) {
79 | super(e);
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/util/Utils.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.util;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.content.res.Resources;
6 | import android.net.Uri;
7 | import android.support.annotation.NonNull;
8 | import android.text.TextUtils;
9 | import android.util.Patterns;
10 |
11 | import org.mariotaku.pass.R;
12 |
13 | import java.io.BufferedReader;
14 | import java.io.IOException;
15 | import java.io.InputStreamReader;
16 | import java.nio.charset.Charset;
17 | import java.util.ArrayList;
18 | import java.util.Collections;
19 | import java.util.List;
20 | import java.util.regex.Matcher;
21 | import java.util.regex.Pattern;
22 |
23 | /**
24 | * Created by mariotaku on 15/10/30.
25 | */
26 | public class Utils {
27 |
28 | public static final Pattern PATTERN_XML_RESOURCE_IDENTIFIER = Pattern.compile("res/xml/([\\w_]+)\\.xml");
29 | public static final Pattern PATTERN_RESOURCE_IDENTIFIER = Pattern.compile("@([\\w_]+)/([\\w_]+)");
30 |
31 | public static List extractLinks(CharSequence text) {
32 | if (TextUtils.isEmpty(text)) return Collections.emptyList();
33 | List links = new ArrayList<>();
34 | Matcher m = Patterns.WEB_URL.matcher(text);
35 | while (m.find()) {
36 | String url = m.group();
37 | links.add(url);
38 | }
39 |
40 | return links;
41 | }
42 |
43 | @NonNull
44 | public static List getHosts(final Context context, final Uri uri, int level) {
45 | final String host = uri.getHost();
46 | if (host == null) return Collections.emptyList();
47 | if (isValidIPAddress(host)) return Collections.singletonList(host);
48 | if (isMultiLevelTLD(context, host)) {
49 | level = level + 1;
50 | }
51 | final String[] hostSegs = host.split(Pattern.quote("."));
52 | List hosts = new ArrayList<>();
53 | for (int i = 0, j = hostSegs.length - level + 1; i < j; i++) {
54 | StringBuilder sb = new StringBuilder();
55 | for (int k = i, l = hostSegs.length; k < l; k++) {
56 | if (k > i) {
57 | sb.append('.');
58 | }
59 | sb.append(hostSegs[k]);
60 | }
61 | hosts.add(0, sb.toString());
62 | }
63 | return hosts;
64 | }
65 |
66 | private static boolean isMultiLevelTLD(final Context context, @NonNull final String host) {
67 | try (BufferedReader br = new BufferedReader(new InputStreamReader(context.getResources()
68 | .openRawResource(R.raw.two_level_tlds), Charset.defaultCharset()))) {
69 | for (String s; (s = br.readLine()) != null; ) {
70 | if (host.endsWith("." + s)) return true;
71 | }
72 | } catch (IOException e) {
73 | }
74 | return false;
75 | }
76 |
77 | public static boolean isValidIPAddress(final String host) {
78 | if (TextUtils.isEmpty(host)) {
79 | return false;
80 | }
81 | return Patterns.IP_ADDRESS.matcher(host).matches();
82 | }
83 |
84 | public static int getResId(final Context context, final String string) {
85 | if (context == null || string == null) return 0;
86 | Matcher m = PATTERN_RESOURCE_IDENTIFIER.matcher(string);
87 | final Resources res = context.getResources();
88 | if (m.matches()) return res.getIdentifier(m.group(2), m.group(1), context.getPackageName());
89 | m = PATTERN_XML_RESOURCE_IDENTIFIER.matcher(string);
90 | if (m.matches()) return res.getIdentifier(m.group(1), "xml", context.getPackageName());
91 | return 0;
92 | }
93 |
94 | public static boolean isWebBrowser(final Context context, final String packageName) {
95 | final Intent intent = new Intent(Intent.ACTION_VIEW);
96 | intent.addCategory(Intent.CATEGORY_BROWSABLE);
97 | intent.setPackage(packageName);
98 | intent.setData(Uri.parse("http://"));
99 | return context.getPackageManager().resolveActivity(intent, 0) != null;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/util/passgen/PassGen.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.util.passgen;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.text.TextUtils;
5 |
6 | import org.mariotaku.pass.util.passgen.impl.HotpPinImpl;
7 | import org.mariotaku.pass.util.passgen.impl.PasswordComposerImpl;
8 | import org.mariotaku.pass.util.passgen.impl.SuperGenPassImpl;
9 |
10 | import java.security.GeneralSecurityException;
11 |
12 | /**
13 | * Created by mariotaku on 15/11/1.
14 | */
15 | public abstract class PassGen {
16 |
17 | protected abstract String generateChecked(@NonNull String pass, @NonNull String domain, int length) throws PassGenException;
18 |
19 | public String generate(String pass, String domain, int length) throws PassGenException {
20 | if (TextUtils.isEmpty(pass) || TextUtils.isEmpty(domain)) {
21 | throw new IllegalArgumentException("Password and domain must not be null or empty");
22 | }
23 | return generateChecked(pass, domain, length);
24 | }
25 |
26 | public static PassGen getInstance(String provider, boolean specialChar) {
27 | switch (provider) {
28 | case "sgp_md5":
29 | return new SuperGenPassImpl("MD5", specialChar);
30 | case "sgp_sha512":
31 | return new SuperGenPassImpl("SHA512", specialChar);
32 | case "pw_composer":
33 | return new PasswordComposerImpl(specialChar);
34 | case "hotp_pin":
35 | return new HotpPinImpl();
36 | }
37 | throw new UnsupportedOperationException("Unsupported provider " + provider);
38 | }
39 |
40 | public static class PassGenException extends GeneralSecurityException {
41 |
42 | public PassGenException(final String message) {
43 | super(message);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/util/passgen/impl/HotpPinImpl.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.util.passgen.impl;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import org.mariotaku.pass.util.passgen.PassGen;
6 | import org.openauthentication.otp.OneTimePasswordAlgorithm;
7 |
8 | import java.security.InvalidKeyException;
9 | import java.security.NoSuchAlgorithmException;
10 |
11 | /**
12 | *
13 | * This generates strong Personal Identification Numbers (PINs).
14 | *
15 | *
16 | *
17 | * PINs generated with this can be used for bank accounts, phone lock screens, ATMs, etc. The
18 | * generator avoids common bad PINs ("1234", "0000", "0007", etc.) detected using a variety of
19 | * techniques.
20 | *
21 | *
22 | * The generation algorithm is a modified version of HOTP which uses the master password for the HMAC
24 | * secret and the domain instead of the moving factor. If a bad PIN is detected, the text " 1" is
25 | * added to the end of the domain and it's recomputed. If a bad PIN is still generated, it suffixes
26 | * " 2" instead and will continue in this way until a good PIN comes out.
27 | *
28 | *
29 | * @author Steve Pomeroy
30 | * @see OneTimePasswordAlgorithm#generateOTPFromText(byte[], byte[], int, boolean, int)
31 | */
32 | public class HotpPinImpl extends PassGen {
33 |
34 | @Override
35 | public String generateChecked(@NonNull String pass, @NonNull String domain, int length)
36 | throws PassGenException {
37 |
38 | if (length < 3 || length > 8) {
39 | throw new IllegalArgumentException("length must be >= 3 and <= 8");
40 | }
41 |
42 | try {
43 | String pin = OneTimePasswordAlgorithm.generateOTPFromText(pass.getBytes(),
44 | domain.getBytes(), length, false, -1);
45 |
46 | if (pin.length() != length) {
47 | throw new PassGenException("PIN generator error; requested length "
48 | + length + ", but got " + pin.length());
49 | }
50 |
51 | int suffix = 0;
52 | int loopOverrun = 0;
53 |
54 | while (isBadPin(pin)) {
55 | final String suffixedDomain = domain + " " + suffix;
56 | pin = OneTimePasswordAlgorithm.generateOTPFromText(pass.getBytes(),
57 | suffixedDomain.getBytes(), length, false, -1);
58 |
59 | loopOverrun++;
60 | suffix++;
61 | if (loopOverrun > 100) {
62 | throw new PassGenException("PIN generator programming error: looped too many times");
63 | }
64 | }
65 | return pin;
66 | } catch (final InvalidKeyException | NoSuchAlgorithmException e) {
67 | throw new RuntimeException(e);
68 | }
69 | }
70 |
71 | /**
72 | * Tests the string to see if it contains a numeric run. For example, "123456", "0000", "9876",
73 | * and "2468" would all match.
74 | *
75 | * @param pin
76 | * @return true if the string is a numeric run
77 | */
78 | public boolean isNumericalRun(String pin) {
79 | final int len = pin.length();
80 | // int[] diff = new int[len - 1];
81 | int prevDigit = Character.digit(pin.charAt(0), 10);
82 | int prevDiff = Integer.MAX_VALUE;
83 | boolean isRun = true; // assume it's true...
84 |
85 | for (int i = 1; isRun && i < len; i++) {
86 | final int digit = Character.digit(pin.charAt(i), 10);
87 |
88 | final int diff = digit - prevDigit;
89 | if (prevDiff != Integer.MAX_VALUE && diff != prevDiff) {
90 | isRun = false; // ... and prove it's false
91 | }
92 |
93 | prevDiff = diff;
94 | prevDigit = digit;
95 | }
96 |
97 | return isRun;
98 | }
99 |
100 | /**
101 | * Tests the string to see if it contains a partial numeric run. Eg. 3000, 5553
102 | *
103 | * @param pin
104 | * @return
105 | */
106 | public boolean isIncompleteNumericalRun(String pin) {
107 | final int len = pin.length();
108 | int consecutive = 0;
109 | char last = pin.charAt(0);
110 | for (int i = 1; i < len; i++) {
111 | final char c = pin.charAt(i);
112 | if (last == c) {
113 | consecutive++;
114 | } else {
115 | consecutive = 0;
116 | }
117 | last = c;
118 | if (consecutive >= 2) {
119 | return true;
120 | }
121 | }
122 | return false;
123 | }
124 |
125 | /**
126 | * This is a hard-coded list of specific PINs that have cultural meaning. While they may be
127 | * improbable, none the less they won't output from the generation.
128 | */
129 | private static final String[] BLACKLISTED_PINS = new String[]{"90210",
130 | "8675309" /* Jenny */,
131 | "1004" /* 10-4 */,
132 | // in this document http://www.datagenetics.com/blog/september32012/index.html
133 | // these were shown to be the least commonly used. Now they won't be used at all.
134 | "8068", "8093", "9629", "6835", "7637", "0738", "8398", "6793", "9480", "8957", "0859",
135 | "7394", "6827", "6093", "7063", "8196", "9539", "0439", "8438", "9047", "8557"};
136 |
137 | /**
138 | * Tests to see if the PIN is a "bad" pin. That is, one that is easily guessable. Essentially,
139 | * this is a blacklist of the most commonly used PINs like "1234", "0000" and "1984".
140 | *
141 | * @param pin
142 | * @return true if the PIN matches the bad PIN criteria
143 | */
144 | public boolean isBadPin(String pin) {
145 | final int len = pin.length();
146 |
147 | // special cases for 4-digit PINs (which are quite common)
148 | if (len == 4) {
149 | final int start = Integer.parseInt(pin.subSequence(0, 2).toString());
150 | final int end = Integer.parseInt(pin.subSequence(2, 4).toString());
151 |
152 | // 19xx pins look like years, so might as well ditch them.
153 | if (start == 19 || (start == 20 && end < 30)) {
154 | return true;
155 | }
156 |
157 | // 1515
158 | if (start == end) {
159 | return true;
160 | }
161 | }
162 |
163 | // find case where all digits are in pairs
164 | // eg 1122 3300447722
165 |
166 | if (len % 2 == 0) {
167 | boolean paired = true;
168 | for (int i = 0; i < len - 1; i += 2) {
169 | if (pin.charAt(i) != pin.charAt(i + 1)) {
170 | paired = false;
171 | }
172 | }
173 | if (paired) {
174 | return true;
175 | }
176 | }
177 |
178 | if (isNumericalRun(pin)) {
179 | return true;
180 | }
181 |
182 | if (isIncompleteNumericalRun(pin)) {
183 | return true;
184 | }
185 |
186 | // filter out special numbers
187 | for (final String blacklisted : BLACKLISTED_PINS) {
188 | if (blacklisted.equals(pin)) {
189 | return true;
190 | }
191 | }
192 |
193 | return false;
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/util/passgen/impl/PasswordComposerImpl.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.util.passgen.impl;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import org.mariotaku.pass.util.passgen.PassGen;
6 |
7 | import java.security.MessageDigest;
8 | import java.security.NoSuchAlgorithmException;
9 |
10 | /**
11 | * Created by mariotaku on 15/11/1.
12 | */
13 | public class PasswordComposerImpl extends PassGen {
14 |
15 | private final MessageDigest md5;
16 | private final boolean specialChar;
17 |
18 | public PasswordComposerImpl(boolean specialChar) {
19 | this.specialChar = specialChar;
20 | try {
21 | md5 = MessageDigest.getInstance("MD5");
22 | } catch (NoSuchAlgorithmException e) {
23 | throw new RuntimeException(e);
24 | }
25 | }
26 |
27 | @Override
28 | protected String generateChecked(@NonNull final String pass, @NonNull final String domain, final int length) throws PassGenException {
29 | if (length < 1 || length > 31) {
30 | throw new IllegalArgumentException("Password length must between 1 and 31 inclusive.");
31 | }
32 | //noinspection RedundantStringConstructorCall
33 | final String md5str = toHexString(md5.digest(new String(pass + ":" + domain).getBytes()));
34 | if (specialChar) {
35 | return md5str.substring(0, length - 1) + ".";
36 | }
37 | return md5str.substring(0, length);
38 | }
39 |
40 | private String toHexString(final byte[] bytes) {
41 | final StringBuilder sb = new StringBuilder();
42 | for (final byte b : bytes) {
43 | String hex = Integer.toHexString(0xFF & b);
44 | if (hex.length() == 1) {
45 | sb.append('0');
46 | }
47 | sb.append(hex);
48 | }
49 | return sb.toString();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/util/passgen/impl/SuperGenPassImpl.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.util.passgen.impl;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.util.Base64;
5 |
6 | import org.mariotaku.pass.util.passgen.PassGen;
7 |
8 | import java.security.MessageDigest;
9 | import java.security.NoSuchAlgorithmException;
10 |
11 | /**
12 | * Created by mariotaku on 15/11/1.
13 | */
14 | public class SuperGenPassImpl extends PassGen {
15 |
16 | private static final int ROUNDS_REQUIREMENT = 10;
17 | private final MessageDigest digest;
18 | private final boolean specialChar;
19 |
20 | public SuperGenPassImpl(String algorithm, boolean specialChar) {
21 | this.specialChar = specialChar;
22 | try {
23 | digest = MessageDigest.getInstance(algorithm);
24 | } catch (NoSuchAlgorithmException e) {
25 | throw new RuntimeException(e);
26 | }
27 | }
28 |
29 | @Override
30 | protected String generateChecked(@NonNull final String pass, @NonNull final String domain, final int length) throws PassGenException {
31 | if (length < 4 || length > 24)
32 | throw new IllegalArgumentException("Password length must between 4 and 24 inclusive");
33 |
34 | String generated = pass + ":" + domain;
35 |
36 | for (int i = 0; i < ROUNDS_REQUIREMENT; i++) {
37 | generated = hash(generated.getBytes());
38 | }
39 | while (!validate(generated, length)) {
40 | generated = hash(generated.getBytes());
41 | }
42 | return generated.substring(0, length);
43 | }
44 |
45 | /* From http://supergenpass.com/about/#PasswordComplexity :
46 | * * Consist of alphanumerics (A-Z, a-z, 0-9)
47 | * * Always start with a lowercase letter of the alphabet
48 | * * Always contain at least one uppercase letter of the alphabet
49 | * * Always contain at least one numeral
50 | * * Can be any length from 4 to 24 characters (default: 10)
51 | */
52 | private boolean validate(final String generated, final int length) {
53 | final CharSequence pw = generated.subSequence(0, length);
54 | final char firstCh = pw.charAt(0);
55 | if (firstCh < 'a' || firstCh > 'z') return false;
56 | boolean hasUppercase = false, hasNumeral = false, hasSpecial = false;
57 | for (int i = 1; i < length; i++) {
58 | final char ch = pw.charAt(i);
59 | if (ch >= 'A' && ch <= 'Z') {
60 | hasUppercase = true;
61 | } else if (ch >= '0' && ch <= '9') {
62 | hasNumeral = true;
63 | } else if (ch == '=' || ch == '/' || ch == '+') {
64 | hasSpecial = true;
65 | }
66 | }
67 | return hasUppercase && hasNumeral && (!specialChar || hasSpecial);
68 | }
69 |
70 | @NonNull
71 | private String hash(final byte[] bytes) {
72 | String hashed = Base64.encodeToString(digest.digest(bytes), Base64.NO_WRAP);
73 | if (specialChar) return hashed;
74 | return hashed.replace('=', 'A').replace('/', '8').replace('+', '9');
75 | }
76 |
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/view/PasswordContainer.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.view;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.content.res.TypedArray;
6 | import android.text.TextUtils;
7 | import android.util.AttributeSet;
8 | import android.view.InflateException;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.FrameLayout;
12 |
13 | import org.mariotaku.pass.Constants;
14 | import org.mariotaku.pass.R;
15 |
16 | import java.lang.reflect.InvocationTargetException;
17 |
18 | /**
19 | * Created by mariotaku on 15/10/30.
20 | */
21 | public class PasswordContainer extends FrameLayout {
22 | public PasswordContainer(final Context context, final AttributeSet attrs, final int defStyleAttr) {
23 | super(context, attrs, defStyleAttr);
24 | }
25 |
26 | public PasswordContainer(final Context context, final AttributeSet attrs) {
27 | super(context, attrs);
28 | }
29 |
30 | public PasswordContainer(final Context context) {
31 | super(context);
32 | }
33 |
34 | @Override
35 | public void addView(final View child, final int index, final ViewGroup.LayoutParams params) {
36 | super.addView(child, index, params);
37 | if (getChildCount() > 1) {
38 | throw new InflateException("PasswordContainer must have one exact child");
39 | }
40 | final View view = getChildAt(0);
41 | final PasswordController controller = getPasswordController();
42 | controller.onAttach(view);
43 | }
44 |
45 | public PasswordController getPasswordController() {
46 | return ((LayoutParams) getChildAt(0).getLayoutParams()).getPasswordController();
47 | }
48 |
49 | @Override
50 | protected boolean checkLayoutParams(final ViewGroup.LayoutParams p) {
51 | return p instanceof LayoutParams;
52 | }
53 |
54 | @Override
55 | public LayoutParams generateLayoutParams(final AttributeSet attrs) {
56 | return new LayoutParams(this, getContext(), attrs);
57 | }
58 |
59 | public static class LayoutParams extends FrameLayout.LayoutParams {
60 |
61 | private final PasswordController passwordController;
62 |
63 | public LayoutParams(final PasswordContainer container, final Context c, final AttributeSet attrs) {
64 | super(c, attrs);
65 | final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.PasswordContainer);
66 | passwordController = parsePasswordController(a.getString(R.styleable.PasswordContainer_layout_passwordController),
67 | container, c, attrs);
68 | a.recycle();
69 | }
70 |
71 | private static PasswordController parsePasswordController(final String className,
72 | final PasswordContainer container,
73 | final Context c, final AttributeSet attrs) {
74 | if (TextUtils.isEmpty(className)) {
75 | throw new InflateException("You must give PasswordContainer's child a PasswordController class");
76 | }
77 | try {
78 | return (PasswordController) Class.forName(className).getConstructor(PasswordContainer.class, Context.class, AttributeSet.class).newInstance(container, c, attrs);
79 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException | ClassCastException e) {
80 | throw new InflateException(e);
81 | }
82 | }
83 |
84 | public PasswordController getPasswordController() {
85 | return passwordController;
86 | }
87 | }
88 |
89 | public static class PasswordController {
90 | private final PasswordContainer container;
91 | private final Context context;
92 | private final SharedPreferences preferences;
93 | private View view;
94 | private OnPasswordEnteredListener listener;
95 |
96 | protected PasswordController(PasswordContainer container, Context context, AttributeSet attributeSet) {
97 | this.container = container;
98 | this.context = context;
99 | this.preferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
100 | }
101 |
102 | public PasswordContainer getContainer() {
103 | return container;
104 | }
105 |
106 | public Context getContext() {
107 | return context;
108 | }
109 |
110 | public SharedPreferences getPreferences() {
111 | return preferences;
112 | }
113 |
114 | public View getView() {
115 | return view;
116 | }
117 |
118 | protected void onAttach(final View view) {
119 | this.view = view;
120 | }
121 |
122 | protected void notifyPasswordEntered(byte[] key) {
123 | if (listener != null) {
124 | listener.onPasswordEntered(key);
125 | }
126 | }
127 |
128 | public void setOnPasswordEnteredListener(final OnPasswordEnteredListener listener) {
129 | this.listener = listener;
130 | }
131 |
132 | public OnPasswordEnteredListener getOnPasswordEnteredListener() {
133 | return listener;
134 | }
135 |
136 | protected final void notifyPasswordWrong() {
137 | final int retryCount = preferences.getInt(Constants.KEY_PASSWORD_RETRY_COUNT, 0);
138 | onPasswordWrong(retryCount);
139 | preferences.edit().putInt(Constants.KEY_PASSWORD_RETRY_COUNT, retryCount + 1).apply();
140 | }
141 |
142 | protected final void resetPasswordRetries() {
143 | preferences.edit().remove(Constants.KEY_PASSWORD_RETRY_COUNT).apply();
144 | }
145 |
146 | protected void onPasswordWrong(int retries) {
147 |
148 | }
149 | }
150 |
151 | public interface OnPasswordEnteredListener {
152 | boolean onPasswordEntered(byte[] key);
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/view/RememberMasterPasswordContainer.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.view;
2 |
3 | import android.content.Context;
4 | import android.content.res.TypedArray;
5 | import android.text.TextUtils;
6 | import android.util.AttributeSet;
7 | import android.view.InflateException;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.view.animation.Animation;
11 | import android.view.animation.AnimationUtils;
12 | import android.widget.FrameLayout;
13 | import android.widget.ViewAnimator;
14 |
15 | import org.mariotaku.pass.R;
16 |
17 | import java.lang.reflect.InvocationTargetException;
18 |
19 | /**
20 | * Created by mariotaku on 15/10/30.
21 | */
22 | public class RememberMasterPasswordContainer extends ViewAnimator {
23 |
24 | private final Animation mSlideOutRightAnimation;
25 | private final Animation mSlideInRightAnimation;
26 | private final Animation mSlideOutLeftAnimation;
27 | private final Animation mSlideInLeftAnimation;
28 |
29 | private PageListener mPageListener;
30 |
31 | public RememberMasterPasswordContainer(final Context context, final AttributeSet attrs) {
32 | super(context, attrs);
33 | mSlideInLeftAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_in_left);
34 | mSlideOutRightAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_out_right);
35 | mSlideInRightAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_in_right);
36 | mSlideOutLeftAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_out_left);
37 | }
38 |
39 | public RememberMasterPasswordContainer(final Context context) {
40 | this(context, null);
41 | }
42 |
43 | @Override
44 | protected boolean checkLayoutParams(final ViewGroup.LayoutParams p) {
45 | return p instanceof LayoutParams;
46 | }
47 |
48 | @Override
49 | public LayoutParams generateLayoutParams(final AttributeSet attrs) {
50 | return new LayoutParams(this, getContext(), attrs);
51 | }
52 |
53 | @Override
54 | public void showNext() {
55 | final LayoutParams lpCurrent = (LayoutParams) getCurrentView().getLayoutParams();
56 | if (lpCurrent.getPageController().onPageNextExit()) {
57 | if (getDisplayedChild() < getChildCount() - 1) {
58 | setInAnimation(mSlideInRightAnimation);
59 | setOutAnimation(mSlideOutLeftAnimation);
60 | super.showNext();
61 | if (mPageListener != null) {
62 | mPageListener.onPageChanged(getDisplayedChild());
63 | }
64 | } else {
65 | if (mPageListener != null) {
66 | mPageListener.onReachedEnd();
67 | }
68 | }
69 | final LayoutParams lpNext = (LayoutParams) getCurrentView().getLayoutParams();
70 | lpNext.getPageController().onPageNextEnter();
71 | }
72 | }
73 |
74 | @Override
75 | public void addView(final View child, final int index, final ViewGroup.LayoutParams params) {
76 | super.addView(child, index, params);
77 | ((LayoutParams) params).getPageController().onAttach(child);
78 | }
79 |
80 | @Override
81 | public void showPrevious() {
82 | final LayoutParams lpCurrent = (LayoutParams) getCurrentView().getLayoutParams();
83 | if (lpCurrent.getPageController().onPagePreviousExit()) {
84 | if (getDisplayedChild() > 0) {
85 | setInAnimation(mSlideInLeftAnimation);
86 | setOutAnimation(mSlideOutRightAnimation);
87 | super.showPrevious();
88 | if (mPageListener != null) {
89 | mPageListener.onPageChanged(getDisplayedChild());
90 | }
91 | } else {
92 | if (mPageListener != null) {
93 | mPageListener.onReachedStart();
94 | }
95 | }
96 | final LayoutParams lpPrevious = (LayoutParams) getCurrentView().getLayoutParams();
97 | lpPrevious.getPageController().onPagePreviousEnter();
98 | }
99 | }
100 |
101 | public PageController findPageControllerById(int id) {
102 | for (int i = 0, j = getChildCount(); i < j; i++) {
103 | final View view = getChildAt(i);
104 | if (id == view.getId()) {
105 | return ((LayoutParams) view.getLayoutParams()).getPageController();
106 | }
107 | }
108 | return null;
109 | }
110 |
111 | public PageListener getPageListener() {
112 | return mPageListener;
113 | }
114 |
115 | public void setPageListener(final PageListener pageListener) {
116 | mPageListener = pageListener;
117 | }
118 |
119 | public interface PageListener {
120 | void onPageChanged(int current);
121 |
122 | void onReachedEnd();
123 |
124 | void onReachedStart();
125 | }
126 |
127 | public static class LayoutParams extends FrameLayout.LayoutParams {
128 |
129 | private final PageController mPageController;
130 |
131 | public LayoutParams(final RememberMasterPasswordContainer container, final Context c, final AttributeSet attrs) {
132 | super(c, attrs);
133 | final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.RememberMasterPasswordContainer);
134 | mPageController = parsePasswordController(a.getString(R.styleable.RememberMasterPasswordContainer_layout_pageController),
135 | container, c, attrs);
136 | a.recycle();
137 | }
138 |
139 | private static PageController parsePasswordController(final String className, final RememberMasterPasswordContainer container,
140 | final Context c, final AttributeSet attrs) {
141 | if (TextUtils.isEmpty(className)) {
142 | throw new InflateException("You must give PasswordContainer's child a PageController class");
143 | }
144 | try {
145 | return (PageController) Class.forName(className).getConstructor(RememberMasterPasswordContainer.class,
146 | Context.class, AttributeSet.class).newInstance(container, c, attrs);
147 | } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException | ClassCastException e) {
148 | throw new InflateException(e);
149 | }
150 | }
151 |
152 | public PageController getPageController() {
153 | return mPageController;
154 | }
155 |
156 | }
157 |
158 | public static class PageController {
159 | private final RememberMasterPasswordContainer container;
160 | private final Context context;
161 | private View view;
162 |
163 | protected PageController(RememberMasterPasswordContainer container, Context context, AttributeSet attributeSet) {
164 | this.container = container;
165 | this.context = context;
166 | }
167 |
168 | public RememberMasterPasswordContainer getContainer() {
169 | return container;
170 | }
171 |
172 | public Context getContext() {
173 | return context;
174 | }
175 |
176 | public View getView() {
177 | return view;
178 | }
179 |
180 |
181 | protected boolean onPageNextExit() {
182 | return true;
183 | }
184 |
185 | protected boolean onPageNextEnter() {
186 | return true;
187 | }
188 |
189 | protected boolean onPagePreviousExit() {
190 | return true;
191 | }
192 |
193 | protected boolean onPagePreviousEnter() {
194 | return true;
195 | }
196 |
197 | protected void onAttach(final View view) {
198 | this.view = view;
199 | }
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/view/controller/PatternPasswordController.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.view.controller;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.SharedPreferences;
6 | import android.text.TextUtils;
7 | import android.util.AttributeSet;
8 | import android.util.Base64;
9 | import android.view.View;
10 | import android.view.inputmethod.InputMethodManager;
11 | import android.widget.Toast;
12 |
13 | import com.eftimoff.patternview.PatternView;
14 | import com.eftimoff.patternview.cells.Cell;
15 |
16 | import org.mariotaku.pass.Constants;
17 | import org.mariotaku.pass.R;
18 | import org.mariotaku.pass.util.MasterPasswordEncrypter;
19 | import org.mariotaku.pass.view.PasswordContainer;
20 |
21 | import java.nio.ByteBuffer;
22 | import java.util.List;
23 |
24 | /**
25 | * Created by mariotaku on 15/10/30.
26 | */
27 | public class PatternPasswordController extends PasswordContainer.PasswordController implements Constants, PatternView.OnPatternDetectedListener, PatternView.OnPatternStartListener {
28 | private final MasterPasswordEncrypter mEncrypter;
29 | private PatternView mPatternView;
30 |
31 | public PatternPasswordController(final PasswordContainer container, final Context context, final AttributeSet attributeSet) {
32 | super(container, context, attributeSet);
33 | mEncrypter = new MasterPasswordEncrypter();
34 | }
35 |
36 | @Override
37 | public void onAttach(final View view) {
38 | super.onAttach(view);
39 | mPatternView = (PatternView) view.findViewById(R.id.password_pattern);
40 | mPatternView.setOnPatternDetectedListener(this);
41 | mPatternView.setOnPatternStartListener(this);
42 | }
43 |
44 | @Override
45 | public void onPatternDetected() {
46 | final List patterns = mPatternView.getPattern();
47 | final byte[] key = getKeyByPatterns(patterns);
48 | try {
49 | final SharedPreferences preferences = getPreferences();
50 | final String patternPw = preferences.getString(KEY_PATTERN_PASSWORD, null);
51 | final String patternIv = preferences.getString(KEY_PATTERN_IV, null);
52 | if (TextUtils.isEmpty(patternPw) || TextUtils.isEmpty(patternIv)) {
53 | // No password set, go to text password
54 | useTextPassword();
55 | return;
56 | }
57 | final byte[] iv = Base64.decode(patternIv, Base64.URL_SAFE);
58 | notifyPasswordEntered(mEncrypter.decrypt(Base64.decode(patternPw, Base64.URL_SAFE), key, iv));
59 | resetPasswordRetries();
60 | } catch (MasterPasswordEncrypter.InternalErrorException | IllegalArgumentException e) {
61 | // TODO Show internal error message
62 | e.printStackTrace();
63 | } catch (MasterPasswordEncrypter.WrongPasswordException e) {
64 | notifyPasswordWrong();
65 | }
66 | mPatternView.clearPattern();
67 | }
68 |
69 | @Override
70 | protected void onPasswordWrong(final int retries) {
71 | mPatternView.setDisplayMode(PatternView.DisplayMode.Wrong);
72 | if (retries >= 4) {
73 | getPreferences().edit().remove(KEY_PATTERN_PASSWORD).apply();
74 | Toast.makeText(getContext(), R.string.pattern_password_cleared, Toast.LENGTH_SHORT).show();
75 | resetPasswordRetries();
76 | useTextPassword();
77 | }
78 | }
79 |
80 | private void useTextPassword() {
81 | final PasswordContainer container = getContainer();
82 | final PasswordContainer.OnPasswordEnteredListener listener = getOnPasswordEnteredListener();
83 | container.removeAllViews();
84 | View.inflate(getContext(), R.layout.layout_password_text, container);
85 | container.getPasswordController().setOnPasswordEnteredListener(listener);
86 | }
87 |
88 | public static byte[] getKeyByPatterns(final List patterns) {
89 | final ByteBuffer bb = ByteBuffer.allocate(patterns.size() * 2 * 4);
90 | for (final Cell pattern : patterns) {
91 | bb.putInt(pattern.getRow());
92 | bb.putInt(pattern.getColumn());
93 | }
94 | return bb.array();
95 | }
96 |
97 | @Override
98 | public void onPatternStart() {
99 | final Activity activity = (Activity) getContext();
100 | final View currentFocus = activity.getCurrentFocus();
101 | InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
102 | if (currentFocus == null || !imm.isActive(currentFocus)) return;
103 | imm.hideSoftInputFromWindow(currentFocus.getWindowToken(), 0);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/view/controller/RememberPasswordFingerprintController.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.view.controller;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
6 | import android.support.v4.os.CancellationSignal;
7 | import android.util.AttributeSet;
8 | import android.util.Base64;
9 | import android.view.View;
10 | import android.widget.TextView;
11 |
12 | import org.mariotaku.pass.Constants;
13 | import org.mariotaku.pass.R;
14 | import org.mariotaku.pass.fragment.RememberMasterPasswordFingerprintFragment;
15 | import org.mariotaku.pass.util.FingerprintCryptoHelper;
16 | import org.mariotaku.pass.util.FingerprintHelper;
17 | import org.mariotaku.pass.view.RememberMasterPasswordContainer;
18 |
19 | import java.io.IOException;
20 | import java.security.GeneralSecurityException;
21 | import java.security.KeyStore;
22 |
23 | import javax.crypto.BadPaddingException;
24 | import javax.crypto.Cipher;
25 | import javax.crypto.IllegalBlockSizeException;
26 | import javax.crypto.KeyGenerator;
27 | import javax.crypto.SecretKey;
28 |
29 | /**
30 | * Created by mariotaku on 15/10/31.
31 | */
32 | public class RememberPasswordFingerprintController extends RememberMasterPasswordContainer.PageController
33 | implements Constants {
34 | private final SharedPreferences mPreferences;
35 | private CancellationSignal cancellationSignal;
36 | private TextView mFingerprintHintView;
37 |
38 | public RememberPasswordFingerprintController(final RememberMasterPasswordContainer container,
39 | final Context context, final AttributeSet attributeSet) {
40 | super(container, context, attributeSet);
41 | mPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
42 | }
43 |
44 | @Override
45 | protected void onAttach(final View view) {
46 | super.onAttach(view);
47 | mFingerprintHintView = (TextView) view.findViewById(R.id.fingerprint_hint);
48 | }
49 |
50 | @Override
51 | protected boolean onPageNextEnter() {
52 | startFingerprintAuthentication();
53 | return true;
54 | }
55 |
56 | @Override
57 | protected boolean onPagePreviousEnter() {
58 | startFingerprintAuthentication();
59 | return true;
60 | }
61 |
62 | private void startFingerprintAuthentication() {
63 | if (cancellationSignal != null && !cancellationSignal.isCanceled()) return;
64 | regenerateKey();
65 | final RememberMasterPasswordContainer.PageListener listener = getContainer().getPageListener();
66 | final RememberPasswordInputController controller = (RememberPasswordInputController) getContainer()
67 | .findPageControllerById(R.id.remember_input_password);
68 | if (listener instanceof RememberMasterPasswordFingerprintFragment) {
69 | FingerprintHelper helper = ((RememberMasterPasswordFingerprintFragment) listener).getFingerprintHelper();
70 | final KeyStore keyStore = FingerprintCryptoHelper.getKeystore();
71 | final Cipher cipher = FingerprintCryptoHelper.getCipher();
72 | final FingerprintManagerCompat.CryptoObject crypto = new FingerprintManagerCompat.CryptoObject(cipher);
73 | cancellationSignal = new CancellationSignal();
74 | try {
75 | keyStore.load(null);
76 | SecretKey key = (SecretKey) keyStore.getKey(FingerprintCryptoHelper.SECURITY_KEY_NAME, null);
77 | cipher.init(Cipher.ENCRYPT_MODE, key);
78 | } catch (GeneralSecurityException | IOException e) {
79 | throw new RuntimeException(e);
80 | }
81 | helper.authenticate(crypto, 0, cancellationSignal, new FingerprintManagerCompat.AuthenticationCallback() {
82 | @Override
83 | public void onAuthenticationError(final int errMsgId, final CharSequence errString) {
84 | mFingerprintHintView.setText(errString);
85 | }
86 |
87 | @Override
88 | public void onAuthenticationHelp(final int helpMsgId, final CharSequence helpString) {
89 | mFingerprintHintView.setText(helpString);
90 | }
91 |
92 | @Override
93 | public void onAuthenticationSucceeded(final FingerprintManagerCompat.AuthenticationResult result) {
94 | try {
95 | final Cipher cipher = result.getCryptoObject().getCipher();
96 | final String encrypted = Base64.encodeToString(cipher.doFinal(controller.getPassword()), Base64.URL_SAFE);
97 | mPreferences.edit().putString(KEY_FINGERPRINT_PASSWORD, encrypted).apply();
98 | mPreferences.edit().putString(KEY_FINGERPRINT_IV, Base64.encodeToString(cipher.getIV(), Base64.URL_SAFE)).apply();
99 | final RememberMasterPasswordContainer container = getContainer();
100 | container.showNext();
101 | } catch (BadPaddingException | IllegalBlockSizeException e) {
102 | showInternalError();
103 | }
104 | }
105 |
106 | @Override
107 | public void onAuthenticationFailed() {
108 | super.onAuthenticationFailed();
109 | }
110 | }, getView().getHandler());
111 | }
112 | }
113 |
114 | private boolean regenerateKey() {
115 | try {
116 | final KeyStore keyStore = FingerprintCryptoHelper.getKeystore();
117 | final KeyGenerator keyGenerator = FingerprintCryptoHelper.getKeyGenerator();
118 | keyStore.load(null);
119 | // Set the alias of the entry in Android KeyStore where the key will appear
120 | // and the constrains (purposes) in the constructor of the Builder
121 | keyGenerator.init(FingerprintCryptoHelper.createKeyGenParameterSpec());
122 | keyGenerator.generateKey();
123 | return true;
124 | } catch (GeneralSecurityException | IOException e) {
125 | throw new RuntimeException(e);
126 | }
127 | }
128 |
129 | private void showInternalError() {
130 |
131 | }
132 |
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/view/controller/RememberPasswordFingerprintNoticeController.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.view.controller;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 |
6 | import org.mariotaku.pass.view.RememberMasterPasswordContainer;
7 |
8 | /**
9 | * Created by mariotaku on 15/10/31.
10 | */
11 | public class RememberPasswordFingerprintNoticeController extends RememberMasterPasswordContainer.PageController {
12 | public RememberPasswordFingerprintNoticeController(final RememberMasterPasswordContainer container,
13 | final Context context, final AttributeSet attributeSet) {
14 | super(container, context, attributeSet);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/view/controller/RememberPasswordInputController.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.view.controller;
2 |
3 | import android.content.Context;
4 | import android.text.TextUtils;
5 | import android.util.AttributeSet;
6 | import android.view.View;
7 | import android.view.inputmethod.InputMethodManager;
8 | import android.widget.EditText;
9 |
10 | import org.mariotaku.pass.R;
11 | import org.mariotaku.pass.view.RememberMasterPasswordContainer;
12 | import org.mariotaku.pass.view.RememberMasterPasswordContainer.PageController;
13 |
14 | /**
15 | * Created by mariotaku on 15/10/31.
16 | */
17 | public class RememberPasswordInputController extends PageController {
18 | private EditText mEditPassword;
19 | private EditText mPasswordConfirm;
20 |
21 | public RememberPasswordInputController(final RememberMasterPasswordContainer container,
22 | final Context context, final AttributeSet attributeSet) {
23 | super(container, context, attributeSet);
24 | }
25 |
26 | @Override
27 | protected void onAttach(final View view) {
28 | super.onAttach(view);
29 | mEditPassword = (EditText) view.findViewById(R.id.edit_password);
30 | mPasswordConfirm = (EditText) view.findViewById(R.id.password_confirm);
31 | }
32 |
33 | @Override
34 | protected boolean onPagePreviousExit() {
35 | hideIME();
36 | return true;
37 | }
38 |
39 | @Override
40 | protected boolean onPagePreviousEnter() {
41 | showIME();
42 | return true;
43 | }
44 |
45 | @Override
46 | protected boolean onPageNextEnter() {
47 | showIME();
48 | return true;
49 | }
50 |
51 | @Override
52 | protected boolean onPageNextExit() {
53 | if (TextUtils.isEmpty(mEditPassword.getText())) {
54 | mEditPassword.setError(getContext().getString(R.string.empty_password));
55 | return false;
56 | }
57 | if (!TextUtils.equals(mPasswordConfirm.getText(), mEditPassword.getText())) {
58 | mPasswordConfirm.setSelection(0, mPasswordConfirm.length());
59 | mPasswordConfirm.setError(getContext().getString(R.string.password_not_match));
60 | return false;
61 | }
62 | hideIME();
63 | return true;
64 | }
65 |
66 | private void showIME() {
67 | if (mEditPassword.requestFocus()) {
68 | final InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
69 | imm.showSoftInput(mEditPassword, InputMethodManager.SHOW_IMPLICIT);
70 | }
71 | }
72 |
73 | private void hideIME() {
74 | mEditPassword.clearFocus();
75 | final InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
76 | imm.hideSoftInputFromWindow(mEditPassword.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
77 | }
78 |
79 | public byte[] getPassword() {
80 | return mEditPassword.getText().toString().getBytes();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/view/controller/RememberPasswordPatternController.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.view.controller;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.util.AttributeSet;
6 | import android.util.Base64;
7 | import android.util.Pair;
8 | import android.view.View;
9 | import android.widget.TextView;
10 |
11 | import com.eftimoff.patternview.PatternView;
12 | import com.eftimoff.patternview.cells.Cell;
13 |
14 | import org.mariotaku.pass.Constants;
15 | import org.mariotaku.pass.R;
16 | import org.mariotaku.pass.util.MasterPasswordEncrypter;
17 | import org.mariotaku.pass.view.RememberMasterPasswordContainer;
18 | import org.mariotaku.pass.view.RememberMasterPasswordContainer.PageController;
19 |
20 | import java.util.List;
21 |
22 | /**
23 | * Created by mariotaku on 15/10/31.
24 | */
25 | public class RememberPasswordPatternController extends PageController implements Constants,
26 | PatternView.OnPatternDetectedListener {
27 | private final MasterPasswordEncrypter mEncrypter;
28 | private final SharedPreferences mPreferences;
29 | private PatternView mPatternView;
30 | private List mPattern;
31 | private TextView mPatternHint;
32 |
33 | public RememberPasswordPatternController(final RememberMasterPasswordContainer container,
34 | final Context context, final AttributeSet attributeSet) {
35 | super(container, context, attributeSet);
36 | mEncrypter = new MasterPasswordEncrypter();
37 | mPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
38 | }
39 |
40 | @Override
41 | protected void onAttach(final View view) {
42 | super.onAttach(view);
43 | mPatternView = ((PatternView) view.findViewById(R.id.pattern_view));
44 | mPatternHint = (TextView) view.findViewById(R.id.pattern_hint);
45 |
46 | mPatternView.setOnPatternDetectedListener(this);
47 | }
48 |
49 | @Override
50 | public void onPatternDetected() {
51 | final List pattern = mPatternView.getPattern();
52 | mPatternView.clearPattern();
53 | if (mPattern != null) {
54 | // Check second pattern matches
55 | if (!mPattern.equals(pattern)) {
56 | mPatternView.setDisplayMode(PatternView.DisplayMode.Wrong);
57 | mPatternHint.setText(R.string.pattern_not_match_hint);
58 | } else {
59 | final RememberMasterPasswordContainer container = getContainer();
60 | final byte[] key = PatternPasswordController.getKeyByPatterns(pattern);
61 | final RememberPasswordInputController controller = (RememberPasswordInputController)
62 | container.findPageControllerById(R.id.remember_input_password);
63 | try {
64 | final Pair encryptedAndIv = mEncrypter.encrypt(controller.getPassword(), key);
65 | final SharedPreferences.Editor editor = mPreferences.edit();
66 | editor.putString(KEY_PATTERN_PASSWORD, Base64.encodeToString(encryptedAndIv.first, Base64.URL_SAFE));
67 | editor.putString(KEY_PATTERN_IV, Base64.encodeToString(encryptedAndIv.second, Base64.URL_SAFE));
68 | editor.apply();
69 | container.showNext();
70 | } catch (MasterPasswordEncrypter.InternalErrorException e) {
71 | mPatternHint.setText(R.string.internal_error);
72 | }
73 | }
74 | } else {
75 | mPatternHint.setText(R.string.pattern_confirm_hint);
76 | }
77 | mPattern = pattern;
78 | }
79 |
80 | @Override
81 | protected boolean onPagePreviousEnter() {
82 | mPatternView.clearPattern();
83 | return true;
84 | }
85 |
86 | @Override
87 | protected boolean onPageNextEnter() {
88 | mPatternView.clearPattern();
89 | return true;
90 | }
91 |
92 | @Override
93 | protected boolean onPageNextExit() {
94 | return true;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/view/controller/RememberPasswordPatternNoticeController.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.view.controller;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 |
6 | import org.mariotaku.pass.view.RememberMasterPasswordContainer;
7 |
8 | /**
9 | * Created by mariotaku on 15/10/31.
10 | */
11 | public class RememberPasswordPatternNoticeController extends RememberMasterPasswordContainer.PageController {
12 | public RememberPasswordPatternNoticeController(final RememberMasterPasswordContainer container,
13 | final Context context, final AttributeSet attributeSet) {
14 | super(container, context, attributeSet);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/org/mariotaku/pass/view/controller/TextPasswordController.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.view.controller;
2 |
3 | import android.content.Context;
4 | import android.text.Editable;
5 | import android.text.TextUtils;
6 | import android.util.AttributeSet;
7 | import android.view.View;
8 | import android.widget.EditText;
9 |
10 | import org.mariotaku.pass.R;
11 | import org.mariotaku.pass.view.PasswordContainer;
12 |
13 | /**
14 | * Created by mariotaku on 15/10/31.
15 | */
16 | public class TextPasswordController extends PasswordContainer.PasswordController implements View.OnClickListener {
17 | private EditText mEditPassword;
18 | private View mPasswordSubmit;
19 |
20 | public TextPasswordController(final PasswordContainer container, final Context context, final AttributeSet attributeSet) {
21 | super(container, context, attributeSet);
22 | }
23 |
24 | @Override
25 | protected void onAttach(final View view) {
26 | super.onAttach(view);
27 | mEditPassword = (EditText) view.findViewById(R.id.edit_password);
28 | mPasswordSubmit = view.findViewById(R.id.password_submit);
29 |
30 | mPasswordSubmit.setOnClickListener(this);
31 | }
32 |
33 | @Override
34 | public void onClick(final View v) {
35 | final Editable text = mEditPassword.getText();
36 | if (TextUtils.isEmpty(text)) {
37 | // Show empty error
38 | return;
39 | }
40 | notifyPasswordEntered(String.valueOf(text).getBytes());
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/org/openauthentication/otp/OneTimePasswordAlgorithm.java:
--------------------------------------------------------------------------------
1 | /*
2 | * OneTimePasswordAlgorithm.java
3 | * OATH Initiative,
4 | * HOTP one-time password algorithm
5 | *
6 | */
7 |
8 | /* Copyright (C) 2004, OATH. All rights reserved.
9 | *
10 | * License to copy and use this software is granted provided that it
11 | * is identified as the "OATH HOTP Algorithm" in all material
12 | * mentioning or referencing this software or this function.
13 | *
14 | * License is also granted to make and use derivative works provided
15 | * that such works are identified as
16 | * "derived from OATH HOTP algorithm"
17 | * in all material mentioning or referencing the derived work.
18 | *
19 | * OATH (Open AuTHentication) and its members make no
20 | * representations concerning either the merchantability of this
21 | * software or the suitability of this software for any particular
22 | * purpose.
23 | *
24 | * It is provided "as is" without express or implied warranty
25 | * of any kind and OATH AND ITS MEMBERS EXPRESSaLY DISCLAIMS
26 | * ANY WARRANTY OR LIABILITY OF ANY KIND relating to this software.
27 | *
28 | * These notices must be retained in any copies of any part of this
29 | * documentation and/or software.
30 | */
31 |
32 | package org.openauthentication.otp;
33 |
34 | import java.security.InvalidKeyException;
35 | import java.security.NoSuchAlgorithmException;
36 |
37 | import javax.crypto.Mac;
38 | import javax.crypto.spec.SecretKeySpec;
39 |
40 | /**
41 | * This class contains static methods that are used to calculate the One-Time Password (OTP) using
42 | * JCE to provide the HMAC-SHA-1.
43 | *
44 | * @author Loren Hart
45 | * @version 1.0
46 | */
47 | public class OneTimePasswordAlgorithm {
48 | private OneTimePasswordAlgorithm() {
49 | }
50 |
51 | // These are used to calculate the check-sum digits.
52 | // 0 1 2 3 4 5 6 7 8 9
53 | private static final int[] doubleDigits = { 0, 2, 4, 6, 8, 1, 3, 5, 7, 9 };
54 |
55 | /**
56 | * Calculates the checksum using the credit card algorithm. This algorithm has the advantage
57 | * that it detects any single mistyped digit and any single transposition of adjacent digits.
58 | *
59 | * @param num
60 | * the number to calculate the checksum for
61 | * @param digits
62 | * number of significant places in the number
63 | *
64 | * @return the checksum of num
65 | */
66 | public static int calcChecksum(long num, int digits) {
67 | boolean doubleDigit = true;
68 | int total = 0;
69 | while (0 < digits--) {
70 | int digit = (int) (num % 10);
71 | num /= 10;
72 | if (doubleDigit) {
73 | digit = doubleDigits[digit];
74 | }
75 | total += digit;
76 | doubleDigit = !doubleDigit;
77 | }
78 | int result = total % 10;
79 | if (result > 0) {
80 | result = 10 - result;
81 | }
82 | return result;
83 | }
84 |
85 | /**
86 | * This method uses the JCE to provide the HMAC-SHA-1 algorithm. HMAC computes a Hashed Message
87 | * Authentication Code and in this case SHA1 is the hash algorithm used.
88 | *
89 | * @param keyBytes
90 | * the bytes to use for the HMAC-SHA-1 key
91 | * @param text
92 | * the message or text to be authenticated.
93 | *
94 | * @throws NoSuchAlgorithmException
95 | * if no provider makes either HmacSHA1 or HMAC-SHA-1 digest algorithms available.
96 | * @throws InvalidKeyException
97 | * The secret provided was not a valid HMAC-SHA-1 key.
98 | *
99 | */
100 |
101 | public static byte[] hmac_sha1(byte[] keyBytes, byte[] text) throws NoSuchAlgorithmException,
102 | InvalidKeyException {
103 | Mac hmacSha1;
104 | try {
105 | hmacSha1 = Mac.getInstance("HmacSHA1");
106 | } catch (final NoSuchAlgorithmException nsae) {
107 | hmacSha1 = Mac.getInstance("HMAC-SHA-1");
108 | }
109 | final SecretKeySpec macKey = new SecretKeySpec(keyBytes, "RAW");
110 | hmacSha1.init(macKey);
111 | return hmacSha1.doFinal(text);
112 | }
113 |
114 | private static final int[] DIGITS_POWER
115 | // 0 1 2 3 4 5 6 7 8
116 | = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000 };
117 |
118 | /**
119 | * This method generates an OTP value for the given set of parameters.
120 | *
121 | * @param secret
122 | * the shared secret
123 | * @param movingFactor
124 | * the counter, time, or other value that changes on a per use basis.
125 | * @param codeDigits
126 | * the number of digits in the OTP, not including the checksum, if any. Valid values
127 | * are between 3 and 8 inclusive.
128 | * @param addChecksum
129 | * a flag that indicates if a checksum digit should be appended to the OTP.
130 | * @param truncationOffset
131 | * the offset into the MAC result to begin truncation. If this value is out of the
132 | * range of 0 ... 15, then dynamic truncation will be used. Dynamic truncation is
133 | * when the last 4 bits of the last byte of the MAC are used to determine the start
134 | * offset.
135 | * @throws NoSuchAlgorithmException
136 | * if no provider makes either HmacSHA1 or HMAC-SHA-1 digest algorithms available.
137 | * @throws InvalidKeyException
138 | * The secret provided was not a valid HMAC-SHA-1 key.
139 | *
140 | * @return A numeric String in base 10 that includes {@link codeDigits} digits plus the optional
141 | * checksum digit if requested.
142 | */
143 | static public String generateOTP(byte[] secret, long movingFactor, int codeDigits,
144 | boolean addChecksum, int truncationOffset) throws NoSuchAlgorithmException,
145 | InvalidKeyException {
146 | // put movingFactor value into text byte array
147 | final byte[] text = new byte[8];
148 | for (int i = text.length - 1; i >= 0; i--) {
149 | text[i] = (byte) (movingFactor & 0xff);
150 | movingFactor >>= 8;
151 | }
152 |
153 | return generateOTPFromText(secret, text, codeDigits, addChecksum, truncationOffset);
154 | }
155 |
156 | /**
157 | * This method generates an OTP value for the given set of parameters.
158 | *
159 | * @param secret
160 | * the shared secret
161 | * @param text
162 | * The data input. This is an alternative to the movingFactor in the other variant of
163 | * this method.
164 | * @param codeDigits
165 | * the number of digits in the OTP, not including the checksum, if any. Valid values
166 | * are between 3 and 8 inclusive.
167 | * @param addChecksum
168 | * a flag that indicates if a checksum digit should be appended to the OTP.
169 | * @param truncationOffset
170 | * the offset into the MAC result to begin truncation. If this value is out of the
171 | * range of 0 ... 15, then dynamic truncation will be used. Dynamic truncation is
172 | * when the last 4 bits of the last byte of the MAC are used to determine the start
173 | * offset.
174 | * @throws NoSuchAlgorithmException
175 | * if no provider makes either HmacSHA1 or HMAC-SHA-1 digest algorithms available.
176 | * @throws InvalidKeyException
177 | * The secret provided was not a valid HMAC-SHA-1 key.
178 | *
179 | * @return A numeric String in base 10 that includes {@link codeDigits} digits plus the optional
180 | * checksum digit if requested.
181 | */
182 | static public String generateOTPFromText(byte[] secret, byte[] text, int codeDigits,
183 | boolean addChecksum, int truncationOffset) throws InvalidKeyException,
184 | NoSuchAlgorithmException {
185 | String result = null;
186 | final int digits = addChecksum ? (codeDigits + 1) : codeDigits;
187 | // compute hmac hash
188 | final byte[] hash = hmac_sha1(secret, text);
189 |
190 | // put selected bytes into result int
191 | int offset = hash[hash.length - 1] & 0xf;
192 | if ((0 <= truncationOffset) && (truncationOffset < (hash.length - 4))) {
193 | offset = truncationOffset;
194 | }
195 | final int binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16)
196 | | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff);
197 |
198 | int otp = binary % DIGITS_POWER[codeDigits];
199 | if (addChecksum) {
200 | otp = (otp * 10) + calcChecksum(otp, codeDigits);
201 | }
202 | result = Integer.toString(otp);
203 | while (result.length() < digits) {
204 | result = "0" + result;
205 | }
206 | return result;
207 | }
208 | }
--------------------------------------------------------------------------------
/src/main/res-localized/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Pass
5 | 轻触传感器或输入密码
6 | 设置
7 | Pass是一个密码生成器,旨在帮助您轻松生成比较安全的密码。\n
8 | Pass只能生成密码,而不会帮您记住密码。因此,如果您忘记您的主密码,我们无法帮您恢复。\n
9 | 任何时候,最重要的安全防线都是您自己,请在日常生活中时时记得提高自己的安全意识。Pass希望能帮您兼得安全与轻松的体验,但是我们并不能保证您绝对的安全。
10 | 域名
11 | 密码
12 | 设置加盐
13 | 增强安全性
14 | 高级
15 | 密码生成器类型
16 | SuperGenPass (MD5)
17 | SuperGenPass (SHA-512)
18 | Password Composer
19 | 记住主密码
20 | 密码长度
21 | 您可以使用手势密码加密并记住您的主密码。\n\n请注意,手势密码不如强密码安全。如果您的设备已经 Root (或者解锁了 Bootloader ),则不建议您再使用此方式。因为通过暴力破解,您的主密码很容易泄露。\n\n在五次错误尝试之后,手势密码或者PIN码将会被清空。此时您必须输入主密码。
22 | 您可以使用指纹加密并记住您的主密码。\n\n请注意,指纹不如强密码安全。
23 | 主密码
24 | 上一步
25 | 下一步
26 | 空密码
27 | 取消
28 | 完成
29 | 画出手势密码
30 | 确认您的手势密码
31 | 手势密码不匹配
32 | 手势密码已清除
33 | 内部错误
34 | 手势密码
35 | 指纹
36 | 返回
37 | 关闭
38 | 复制密码
39 | 复制 PIN
40 | 不支持指纹功能
41 | 安全 -> 指纹 来设置指纹。]]>
42 | PIN 长度
43 | 触摸扫描器
44 | 复制密码有效期
45 | 自动清除复制的密码
46 | 密码已复制
47 | PIN
48 | 无法识别
49 | 需要输入域名
50 | 输入密码
51 | 不支持的标签
52 | 未注册的标签
53 | 专业版功能
54 | NFC 标签
55 | 添加卡片
56 | 域名
57 | 名称
58 | 点击生成密码
59 | 密码输入助手
60 | 复制的密码
61 | 这个界面不支持自动填充,请粘贴密码
62 | 自动填充密码
63 | 标签 %s
64 |
65 |
--------------------------------------------------------------------------------
/src/main/res/anim/slide_in_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
24 |
26 |
27 |
--------------------------------------------------------------------------------
/src/main/res/anim/slide_in_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
24 |
26 |
27 |
--------------------------------------------------------------------------------
/src/main/res/anim/slide_out_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
24 |
26 |
27 |
--------------------------------------------------------------------------------
/src/main/res/anim/slide_out_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
24 |
26 |
27 |
--------------------------------------------------------------------------------
/src/main/res/drawable-hdpi/ic_fp_40px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-hdpi/ic_fp_40px.png
--------------------------------------------------------------------------------
/src/main/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/drawable-hdpi/ic_pw_40px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-hdpi/ic_pw_40px.png
--------------------------------------------------------------------------------
/src/main/res/drawable-hdpi/ic_qs_tile_mdpi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-hdpi/ic_qs_tile_mdpi.png
--------------------------------------------------------------------------------
/src/main/res/drawable-hdpi/ic_stat_password.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-hdpi/ic_stat_password.png
--------------------------------------------------------------------------------
/src/main/res/drawable-mdpi/ic_fp_40px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-mdpi/ic_fp_40px.png
--------------------------------------------------------------------------------
/src/main/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/drawable-mdpi/ic_pw_40px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-mdpi/ic_pw_40px.png
--------------------------------------------------------------------------------
/src/main/res/drawable-mdpi/ic_qs_tile_mdpi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-mdpi/ic_qs_tile_mdpi.png
--------------------------------------------------------------------------------
/src/main/res/drawable-mdpi/ic_stat_password.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-mdpi/ic_stat_password.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xhdpi/ic_fp_40px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xhdpi/ic_fp_40px.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xhdpi/ic_pw_40px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xhdpi/ic_pw_40px.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xhdpi/ic_qs_tile_mdpi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xhdpi/ic_qs_tile_mdpi.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xhdpi/ic_stat_password.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xhdpi/ic_stat_password.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/ic_fp_40px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxhdpi/ic_fp_40px.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/ic_pw_40px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxhdpi/ic_pw_40px.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/ic_qs_tile_mdpi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxhdpi/ic_qs_tile_mdpi.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxhdpi/ic_stat_password.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxhdpi/ic_stat_password.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxxhdpi/ic_fp_40px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxxhdpi/ic_fp_40px.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxxhdpi/ic_pw_40px.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxxhdpi/ic_pw_40px.png
--------------------------------------------------------------------------------
/src/main/res/drawable-xxxhdpi/ic_qs_tile_mdpi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mariotaku/Pass/0274bb327128027c8d8c15bfe3f5681f689b95d1/src/main/res/drawable-xxxhdpi/ic_qs_tile_mdpi.png
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_back.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_clear.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_copy.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_info.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_action_settings.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/drawable/ic_btn_ok.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/src/main/res/layout/activity_pass_gen_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
20 |
21 |
27 |
28 |
39 |
40 |
46 |
47 |
58 |
59 |
68 |
69 |
73 |
74 |
84 |
85 |
86 |
94 |
95 |
96 |
102 |
103 |
109 |
110 |
124 |
125 |
129 |
130 |
144 |
145 |
146 |
156 |
157 |
172 |
173 |
189 |
190 |
191 |
206 |
207 |
222 |
223 |
224 |
225 |
226 |
--------------------------------------------------------------------------------
/src/main/res/layout/dialog_add_card.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
16 |
17 |
22 |
23 |
29 |
30 |
36 |
--------------------------------------------------------------------------------
/src/main/res/layout/fragment_remember_master_password_fingerprint.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
22 |
23 |
30 |
31 |
32 |
37 |
38 |
42 |
43 |
50 |
51 |
58 |
59 |
60 |
61 |
66 |
67 |
78 |
79 |
85 |
86 |
87 |
88 |
95 |
96 |
103 |
104 |
111 |
112 |
--------------------------------------------------------------------------------
/src/main/res/layout/fragment_remember_master_password_pattern.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
16 |
17 |
22 |
23 |
30 |
31 |
32 |
37 |
38 |
42 |
43 |
50 |
51 |
58 |
59 |
60 |
61 |
66 |
67 |
78 |
79 |
88 |
89 |
90 |
91 |
98 |
99 |
106 |
107 |
114 |
115 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_dialog_title_pass_gen.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
17 |
18 |
28 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_password_pattern.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
20 |
--------------------------------------------------------------------------------
/src/main/res/layout/layout_password_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
20 |
21 |
32 |
--------------------------------------------------------------------------------
/src/main/res/menu/menu_pass_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/main/res/menu/menu_tags_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | @string/sgp_md5_name
5 | @string/sgp_sha512_name
6 | @string/pw_composer_name
7 |
8 |
9 | sgp_md5
10 | sgp_sha512
11 | pw_composer
12 |
13 |
14 | 10000
15 | 15000
16 | 30000
17 | 60000
18 |
19 |
20 | 10s
21 | 15s
22 | 30s
23 | 1min
24 |
25 |
--------------------------------------------------------------------------------
/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 8dp
4 | 24dp
5 | 16dp
6 | 48dp
7 |
--------------------------------------------------------------------------------
/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Pass
3 | Touch sensor or input password
4 | Settings
5 | Pass is a password generator to help you generate relatively safety password easily.\nPass can only generate password instead of store password. So if you forgot your master password, we can\'t recover it for you.\nYour are the most important part for your safety. Please take care of your safety anytime in your life. We wish to help you get both simplicity and safety, but absolute safety is not guaranteed.
6 | Domain
7 | Password
8 | Set salt
9 | Enhanced security
10 | Advanced
11 | Password generator type
12 | SuperGenPass (MD5)
13 | SuperGenPass (SHA-512)
14 | Password Composer
15 | Remember master password
16 | Password length
17 | You can use pattern to encrypt and remember your master password. Please note that pattern lock is not secure as strong password.\n\nIt\'s not suggested to use pattern lock if your device was rooted (or bootloader unlocked) because your master password is vulnerable to brute-force attack on such devices.\n\nAfter 5 error attempts, pattern lock will be cleared, then you will have to use your master password.
18 | You can use fingerprint to encrypt and remember your master password.\n\nPlease note that fingerprint is not secure as strong password.
19 | Master password
20 | Previous
21 | Next
22 | Empty password
23 | Cancel
24 | Finish
25 | Draw pattern
26 | Confirm your pattern
27 | Pattern not match
28 | Pattern password cleared
29 | Internal error
30 | Pattern lock
31 | Fingerprint
32 | Back
33 | Close
34 | Copy password
35 | Copy pin
36 | Fingerprint not supported
37 | Secure -> Fingerprint to set up a fingerprint.]]>
38 | PIN length
39 | Touch scanner
40 | Copied password validity
41 | Clear copied password automatically
42 | Password copied
43 | PIN
44 | Couldn\'t recognize you
45 | Domain name required
46 | Input password
47 | Unsupported tag
48 | Unregistered tag
49 | Pro feature
50 | NFC tags
51 | Add card
52 | Host
53 | Name
54 | Click to generate password
55 | Password input helper
56 | Copied password
57 | Automatic filling is not supported in this screen, paste password instead.
58 | Auto fill passwords
59 | Tag %s
60 | Info
61 | Password confirm
62 | Password not match
63 | Include special characters
64 |
65 |
--------------------------------------------------------------------------------
/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/main/res/xml/accessibility_service.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/main/res/xml/pref_general.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
11 |
12 |
13 |
16 |
19 |
20 |
21 |
25 |
29 |
30 |
31 |
37 |
41 |
47 |
53 |
60 |
61 |
--------------------------------------------------------------------------------
/src/release/java/org/mariotaku/pass/util/DebugModeUtils.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass.util;
2 |
3 | import android.app.Application;
4 |
5 | /**
6 | * Created by mariotaku on 15/11/19.
7 | */
8 | public class DebugModeUtils {
9 |
10 | public static void init(Application application) {
11 | // No-op
12 | }
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/src/test/java/org/mariotaku/pass/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package org.mariotaku.pass;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------