10 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/menu/pattern_data_list.xml:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/preferences/SettingsFragment.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android.preferences;
2 |
3 | import android.os.Bundle;
4 | import android.preference.PreferenceFragment;
5 | import org.passwordmaker.android.R;
6 |
7 | public class SettingsFragment extends PreferenceFragment {
8 | @Override
9 | public void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 |
12 | // Load the preferences from an XML resource
13 | addPreferencesFromResource(R.xml.preferences);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/layout/activity_patterndata_list.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/TextWatcherAdapter.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.text.Editable;
4 | import android.text.TextWatcher;
5 |
6 | public abstract class TextWatcherAdapter implements TextWatcher {
7 | @Override
8 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
9 |
10 | }
11 |
12 | @Override
13 | public void onTextChanged(CharSequence s, int start, int before, int count) {
14 |
15 | }
16 |
17 | @Override
18 | public void afterTextChanged(Editable s) {
19 |
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/values-large/refs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 | @layout/activity_account_twopane
12 | @layout/activity_patterndata_twopane
13 |
14 |
15 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/values-sw600dp/refs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 | @layout/activity_account_twopane
12 | @layout/activity_patterndata_twopane
13 |
14 |
15 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/layout/toolbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/layout/fragment_account_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/AndroidGlobalSettings.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import org.daveware.passwordmaker.GlobalSettingKey;
4 |
5 | public class AndroidGlobalSettings {
6 | public final static GlobalSettingKey FAVORITES = new GlobalSettingKey("NS1:favorites", "");
7 | public final static GlobalSettingKey MASTER_PASSWORD_HASH = new GlobalSettingKey("NS1:MASTER_PWD_HASH", "");
8 | public final static GlobalSettingKey STORE_MASTER_PASSWORD_HASH = new GlobalSettingKey("NS1:STORE_MASTER_PWD_HASH", "false");
9 | public final static GlobalSettingKey MASTER_PASSWORD_SALT = new GlobalSettingKey("NS1:MASTER_PWD_SALT", "");
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/layout/activity_account_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 |
15 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/menu/account_list.xml:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/Logtags.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | public enum Logtags {
4 |
5 |
6 | MAIN_ACTIVITY("MAIN"),
7 | ACCOUNT_DETAIL_ACTIVITY("ADA"),
8 | ACCOUNT_DETAIL_FRAGMENT("ADF"),
9 | ACCOUNT_LIST_ACTIVITY("ALA"),
10 | ACCOUNT_LIST_FRAGMENT("ALF"),
11 | CLASSIC_SETTINGS_IMPORTER("CSI"),
12 | IMPORT_EXPORT_RDF("IMEX"),
13 | PATTERN_DATA_DETAIL_ACTIVITY("PDDA"),
14 | PATTERN_DATA_DETAIL_FRAGMENT("PDDF"),
15 | PATTERN_DATA_LIST_ACTIVITY("PDLA"),
16 | PATTERN_DATA_LIST_FRAGMENT("PDLF"),
17 | PWM_APPLICATION("PAPP")
18 | ;
19 |
20 | private final String tag;
21 | Logtags(String tag) {
22 | this.tag = "PWM/" + tag;
23 | }
24 |
25 |
26 | public String getTag() {
27 | return tag;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/AndroidRDFDatabaseWriter.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import org.daveware.passwordmaker.RDFDatabaseWriter;
4 | import org.daveware.passwordmaker.xmlwrappers.XmlIOException;
5 | import org.daveware.passwordmaker.xmlwrappers.XmlStreamWriter;
6 | import org.passwordmaker.android.xmlwrappers.AndroidXmlStreamWriter;
7 |
8 | import java.io.Writer;
9 |
10 | /**
11 | * This is a specialized version of the default RDFDatabaseWriter that just changes the underlining XMLStreamWriter
12 | * to one that exist in the Android ecosystem.
13 | */
14 | public class AndroidRDFDatabaseWriter extends RDFDatabaseWriter {
15 | @Override
16 | protected XmlStreamWriter newXmlStreamWriter(Writer writer) throws XmlIOException {
17 | return new AndroidXmlStreamWriter(writer);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/passwordmaker/proguard-rules.txt:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /opt/android-sdk-macosx/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the ProGuard
5 | # include property in project.properties.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 | -dontobfuscate
12 |
13 | # If your project uses WebView with JS, uncomment the following
14 | # and specify the fully qualified class name to the JavaScript interface
15 | # class:
16 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
17 | # public *;
18 | #}
19 | -dontwarn javax.naming.**
20 | -dontwarn javax.annotation.**
21 | -dontwarn javax.xml.stream.**
22 | -dontwarn sun.misc.Unsafe
23 | -keep class org.spongycastle.**
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/layout/activity_edit_favorites.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
20 |
21 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/menu/account_list_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/menu/main.xml:
--------------------------------------------------------------------------------
1 |
25 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
16 |
20 |
24 |
28 |
--------------------------------------------------------------------------------
/passwordmaker/set_signing_env_vars.sh:
--------------------------------------------------------------------------------
1 | # this file must not be ran as a separate process in order to modify your environment.
2 | # intead use this file at the CLI by doing: source set_signing_env_vars.sh
3 | echo -n "Keystore file: "
4 | if [ -n "$PASSWORDMAKER_KEYSTORE_FILE" ]; then
5 | echo -n "($PASSWORDMAKER_KEYSTORE_FILE) "
6 | fi
7 | read PASSWORDMAKER_KEYSTORE_FILE_TMP
8 | if [ -n "$PASSWORDMAKER_KEYSTORE_FILE_TMP" ]; then
9 | PASSWORDMAKER_KEYSTORE_FILE=$PASSWORDMAKER_KEYSTORE_FILE_TMP
10 | unset PASSWORDMAKER_KEYSTORE_FILE_TMP
11 | fi
12 | echo "File set to: $PASSWORDMAKER_KEYSTORE_FILE"
13 | echo -n "Keystore password: "
14 | read -s PASSWORDMAKER_KEYSTORE_PASSWORD
15 | echo -n "\nKey alias: "
16 |
17 | if [ -n "$PASSWORDMAKER_KEYSTORE_KEY_ALIAS" ]; then
18 | echo -n "($PASSWORDMAKER_KEYSTORE_KEY_ALIAS) "
19 | fi
20 | read PASSWORDMAKER_KEYSTORE_KEY_ALIAS_TMP
21 | if [ -n "$PASSWORDMAKER_KEYSTORE_KEY_ALIAS_TMP" ]; then
22 | PASSWORDMAKER_KEYSTORE_KEY_ALIAS=$PASSWORDMAKER_KEYSTORE_KEY_ALIAS_TMP
23 | unset PASSWORDMAKER_KEYSTORE_KEY_ALIAS_TMP
24 | fi
25 | echo "Key alias set to: $PASSWORDMAKER_KEYSTORE_KEY_ALIAS"
26 | echo -n "Key password: "
27 | read -s PASSWORDMAKER_KEYSTORE_KEY_PASSWORD
28 | echo
29 |
30 | export PASSWORDMAKER_KEYSTORE_FILE
31 | export PASSWORDMAKER_KEYSTORE_PASSWORD
32 | export PASSWORDMAKER_KEYSTORE_KEY_ALIAS
33 | export PASSWORDMAKER_KEYSTORE_KEY_PASSWORD
34 |
--------------------------------------------------------------------------------
/assets/store/store-listing.txt:
--------------------------------------------------------------------------------
1 | #Title
2 | PasswordMaker Pro
3 |
4 | #Short Description
5 | PasswordMaker creates unique, secure passwords that are not needed to be saved.
6 |
7 | #Full Description
8 | ONE PASSWORD TO RULE THEM ALL
9 |
10 | PasswordMaker creates unique, secure passwords that are very easy for you to retrieve but no one else. Nothing is stored anywhere, anytime, so there's nothing to be hacked, lost, or stolen.
11 |
12 | You provide PasswordMaker two pieces of information: a "master password" -- that one, single password you like -- and the URL of the website requiring a password. PasswordMaker will then generate a unique password using a one-way hash, which will protect your master password.
13 |
14 | Compatible with the other versions of Passwordmaker for other devices and computers.
15 |
16 | See http://android.passwordmaker.org/ for more information on PasswordMaker.
17 |
18 | If you have questions, comments or concerns please post them on them on the github issue tracker by following the link above. I do not monitor the ratings and comments here. Also go to the page: http://passwordmaker.org to find other versions for other platforms like the chrome or firefox versions of passwordmaker.
19 |
20 | # Categorization
21 | Application Type: Applications
22 | Category: Tools
23 | Content rating: Everyone
24 | Website: http://android.passwordmaker.org
25 | Email: pwmp.for.android@gmail.com
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/layout/dialog_set_pwd_hash.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
12 |
22 |
32 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/layout/activity_patterndata_twopane.xml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
23 |
24 |
31 |
32 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/EditFavoritesFragment.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.app.ListFragment;
4 | import android.os.Bundle;
5 | import android.widget.ArrayAdapter;
6 | import android.widget.ListView;
7 | import org.passwordmaker.android.widgets.SwipeDismissListViewTouchListener;
8 |
9 | public class EditFavoritesFragment extends ListFragment implements SwipeDismissListViewTouchListener.DismissCallbacks {
10 |
11 | private ArrayAdapter favorites;
12 | // We require to keep a reference for the listener
13 | @SuppressWarnings("FieldCanBeLocal")
14 | private SwipeDismissListViewTouchListener touchListener;
15 |
16 |
17 | @Override
18 | public void onCreate(Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 |
21 | favorites = new ArrayAdapter(
22 | getActivity(),
23 | android.R.layout.simple_list_item_activated_1,
24 | android.R.id.text1, PwmApplication.getInstance().getAccountManager().getFavoriteUrls());
25 | setListAdapter(favorites);
26 | }
27 |
28 | @Override
29 | public void onStart() {
30 | super.onStart();
31 | ListView listView = getListView();
32 | touchListener = new SwipeDismissListViewTouchListener(listView, this);
33 | listView.setOnTouchListener(touchListener);
34 | listView.setOnScrollListener(touchListener.makeScrollListener());
35 | }
36 |
37 | public void addItem(String title) {
38 | favorites.add(title);
39 | }
40 |
41 | @Override
42 | public boolean canDismiss(int position) {
43 | return true;
44 | }
45 |
46 | @Override
47 | public void onDismiss(ListView listView, int[] reverseSortedPositions) {
48 | for (int position : reverseSortedPositions) {
49 | favorites.remove(favorites.getItem(position));
50 | }
51 | favorites.notifyDataSetChanged();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/layout/activity_account_twopane.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
20 |
21 |
31 |
32 |
39 |
40 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/preferences/MasterPasswordPreference.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android.preferences;
2 |
3 | import android.content.Context;
4 | import android.preference.DialogPreference;
5 | import android.util.AttributeSet;
6 | import android.view.View;
7 | import android.widget.EditText;
8 | import android.widget.Toast;
9 | import org.daveware.passwordmaker.AccountManager;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.passwordmaker.android.PwmApplication;
12 | import org.passwordmaker.android.R;
13 |
14 | public class MasterPasswordPreference extends DialogPreference {
15 | private View dlgView;
16 | public MasterPasswordPreference(Context context, AttributeSet attrs) {
17 | super(context, attrs);
18 | setDialogLayoutResource(R.layout.dialog_set_pwd_hash);
19 | setPositiveButtonText(android.R.string.ok);
20 | setNegativeButtonText(android.R.string.cancel);
21 |
22 | setDialogIcon(null);
23 | }
24 |
25 | @Override
26 | protected void onBindDialogView(@NotNull View view) {
27 | super.onBindDialogView(view);
28 | dlgView = view;
29 | }
30 |
31 | @Override
32 | protected void onDialogClosed(boolean positiveResult) {
33 | // When the user selects "OK", persist the new value
34 | if (positiveResult) {
35 | EditText password = (EditText)dlgView.findViewById(R.id.password);
36 | EditText confirmed = (EditText)dlgView.findViewById(R.id.confirm_password);
37 | if ( ! password.getText().toString().equals(confirmed.getText().toString())) {
38 | Toast.makeText(getContext(), "Password Mismatch", Toast.LENGTH_SHORT).show();
39 | return;
40 | }
41 | AccountManager accountManager = PwmApplication.getInstance().getAccountManager();
42 | if ( password.getText().length() == 0 ) {
43 | accountManager.disablePasswordHash();
44 | persistBoolean(false);
45 | } else {
46 | accountManager.setCurrentPasswordHashPassword(password.getText().toString());
47 | persistBoolean(true);
48 | }
49 | PwmApplication.getInstance().saveSettings(getContext());
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/passwordmaker/src/androidTest/resources/test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
7 |
8 |
13 |
19 |
25 |
26 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/SettingsActivity.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.app.ActionBar;
4 | import android.app.Activity;
5 | import android.os.Bundle;
6 | import android.support.v4.app.NavUtils;
7 | import android.view.MenuItem;
8 | import org.passwordmaker.android.preferences.SettingsFragment;
9 |
10 | // Unused is suppressed just to have a reference for preferences
11 | @SuppressWarnings("UnusedDeclaration")
12 | public class SettingsActivity extends Activity {
13 |
14 | public static final String KEY_SHOW_USERNAME = "pref_showUsername";
15 | public static final String KEY_SAVED_LENGTH = "pref_saveInputs";
16 | public static final String KEY_SHOW_PASS_STRENGTH = "pref_showPasswordStrength";
17 | public static final String KEY_MASTER_PASSWORD_HASH = "pref_masterPasswordHash";
18 | public static final String KEY_AUTO_ADD_INPUT_FAVS = "pref_AutoAddTextToFavorites";
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | // Show the Up button in the action bar.
24 | setDisplayHomeAsUpEnabled();
25 |
26 | getFragmentManager().beginTransaction()
27 | .replace(android.R.id.content, new SettingsFragment())
28 | .commit();
29 | }
30 |
31 | private void setDisplayHomeAsUpEnabled() {
32 | // prevent the possible nullpointer if getActionBar returns null.
33 | ActionBar actionBar = getActionBar();
34 | if ( actionBar != null ) actionBar.setDisplayHomeAsUpEnabled(true);
35 | }
36 |
37 | @Override
38 | public boolean onOptionsItemSelected(MenuItem item) {
39 | int id = item.getItemId();
40 | if (id == android.R.id.home) {
41 | // This ID represents the Home or Up button. In the case of this
42 | // activity, the Up button is shown. Use NavUtils to allow users
43 | // to navigate up one level in the application structure. For
44 | // more details, see the Navigation pattern on Android Design:
45 | //
46 | // http://developer.android.com/design/patterns/navigation.html#up-vs-back
47 | //
48 | NavUtils.navigateUpFromSameTask(this);
49 | return true;
50 | }
51 | return super.onOptionsItemSelected(item);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/passwordmaker/src/androidTest/java/org/passwordmaker/PwmTest.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker;
2 |
3 | import org.daveware.passwordmaker.*;
4 |
5 | import java.security.Security;
6 |
7 | import static org.passwordmaker.TestUtils.saToString;
8 |
9 | /**
10 | * The purpose of this test is to make sure that the hash functions are available on the Android Emulator.
11 | *
12 | * The real unit tests for this functionality is in the passwordmaker-je-lib
13 | *
14 | */
15 | public class PwmTest extends junit.framework.TestCase {
16 | static {
17 | PasswordMaker.setDefaultCryptoProvider("SC");
18 | Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
19 | }
20 |
21 | protected void performTest(AlgorithmType algorithmType, boolean useHMac, String expected) throws Exception {
22 | Account profile = new Account();
23 | profile.setCharacterSet(CharacterSets.ALPHANUMERIC);
24 | profile.setAlgorithm(algorithmType);
25 | profile.setHmac(useHMac);
26 | profile.setLength(8);
27 | profile.clearUrlComponents();
28 | profile.addUrlComponent(Account.UrlComponents.Domain);
29 |
30 | SecureUTF8String masterPassword = new SecureUTF8String("happy");
31 |
32 | PasswordMaker pwm = new PasswordMaker();
33 | assertEquals(expected, saToString(pwm.makePassword(masterPassword, profile, "google.com")));
34 | }
35 |
36 | public void testMD5() throws Exception {
37 | performTest(AlgorithmType.MD5, false, "HRdgNiyh");
38 | }
39 | public void testMD4() throws Exception {
40 | performTest(AlgorithmType.MD4, false, "HtzLxeLD");
41 | }
42 | public void testRIPEMD160() throws Exception {
43 | performTest(AlgorithmType.RIPEMD160, false, "joh9YCZc");
44 | }
45 | public void testSHA1() throws Exception {
46 | performTest(AlgorithmType.SHA1, false, "iEXyQtf6");
47 | }
48 | public void testSHA256() throws Exception {
49 | performTest(AlgorithmType.SHA256, false, "w8BStwWP");
50 | }
51 | public void testMD5HMac() throws Exception {
52 | performTest(AlgorithmType.MD5, true, "BdVB2Ye3");
53 | }
54 | public void testMD4HMac() throws Exception {
55 | performTest(AlgorithmType.MD4, true, "FYrXl6y9");
56 | }
57 | public void testRIPEMD160HMac() throws Exception {
58 | performTest(AlgorithmType.RIPEMD160, true, "IeMWw25Q");
59 | }
60 | public void testSHA1HMac() throws Exception {
61 | performTest(AlgorithmType.SHA1, true, "YqVH5OAk");
62 | }
63 | public void testSHA256HMac() throws Exception {
64 | performTest(AlgorithmType.SHA256, true, "Qljpvcsf");
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/layout/activity_import_export_rdf.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
20 |
21 |
26 |
27 |
33 |
34 |
35 |
43 |
44 |
53 |
54 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/AlgorithmSelectionValues.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import org.daveware.passwordmaker.Account;
4 | import org.daveware.passwordmaker.AlgorithmType;
5 |
6 | import java.util.NoSuchElementException;
7 |
8 | @SuppressWarnings("UnusedDeclaration")
9 | public enum AlgorithmSelectionValues {
10 | // The stringResourcePosition must match that of the strings.xml HashAlgos array index
11 |
12 | MD4(AlgorithmType.MD4, false, true, 0),
13 | HMAC_MD4(AlgorithmType.MD4, true, true, 1),
14 | MD5(AlgorithmType.MD5, false, true, 2),
15 | MD5_06(AlgorithmType.MD5, false, false, 3),
16 | HMAC_MD5(AlgorithmType.MD5, true, true, 4),
17 | HMAC_MD5_06(AlgorithmType.MD5, true, false, 5),
18 | SHA1(AlgorithmType.SHA1, false, true, 6),
19 | HMAC_SHA1(AlgorithmType.SHA1, true, true, 7),
20 | SHA256(AlgorithmType.SHA256, false, true, 8),
21 | HMAC_SHA256(AlgorithmType.SHA256, true, true, 9),
22 | RIPEMD160(AlgorithmType.RIPEMD160, false, true, 10),
23 | HMAC_RIPEMD160(AlgorithmType.RIPEMD160, true, true, 11)
24 | ;
25 |
26 |
27 | private final AlgorithmType aglo;
28 | private final boolean isHMac;
29 | private final boolean isTrimmed;
30 | private final int stringResourcePosition;
31 |
32 | AlgorithmSelectionValues(AlgorithmType aglo, boolean isHMac, boolean isTrimmed, int stringResourcePosition) {
33 | this.aglo = aglo;
34 | this.isHMac = isHMac;
35 | this.isTrimmed = isTrimmed;
36 | this.stringResourcePosition = stringResourcePosition;
37 | }
38 |
39 | public AlgorithmType getAlgo() {
40 | return aglo;
41 | }
42 |
43 | public boolean isHMac() {
44 | return isHMac;
45 | }
46 |
47 | public boolean isTrimmed() {
48 | return isTrimmed;
49 | }
50 |
51 | public int getStringResourcePosition() {
52 | return stringResourcePosition;
53 | }
54 |
55 | public static AlgorithmSelectionValues getByPosition(int index) {
56 | for (AlgorithmSelectionValues v : values()) {
57 | if ( v.stringResourcePosition == index ) return v;
58 | }
59 | throw new NoSuchElementException("Invalid index: " + index);
60 | }
61 |
62 | public static AlgorithmSelectionValues getFromAccount(Account account) {
63 | for (AlgorithmSelectionValues v : values()) {
64 | if ( v.isHMac == account.isHmac() &&
65 | v.isTrimmed == account.isTrim() &&
66 | v.getAlgo() == account.getAlgorithm() ) return v;
67 | }
68 | throw new NoSuchElementException("Could not find algorithm selection combination from account '"
69 | + account.getId() + "(" + account.getName() + ")' setup");
70 | }
71 |
72 | public void setAccountSettings(Account account) {
73 | account.setHmac(isHMac);
74 | account.setTrim(isTrimmed);
75 | account.setAlgorithm(aglo);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/adapters/SubstringArrayAdapter.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android.adapters;
2 |
3 | import android.content.Context;
4 | import android.widget.ArrayAdapter;
5 | import android.widget.Filter;
6 | import com.google.common.base.Predicate;
7 | import com.google.common.collect.Collections2;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | * This class would not be thread safe. Even though ArrayAdapter is. But due to the fact they don't share some of its
14 | * internals, I can't without basically rewriting the entire ArrayAdapter make this thread safe. And since my use case
15 | * does not require it, it is not.
16 | */
17 |
18 | public class SubstringArrayAdapter extends ArrayAdapter {
19 | private List objects;
20 | private final List original_objects;
21 | private final SubStringFilter myFilter = new SubStringFilter();
22 |
23 | public SubstringArrayAdapter(Context context, @SuppressWarnings("SameParameterValue") int resource, List objects) {
24 | super(context, resource, objects);
25 | this.original_objects = this.objects = objects;
26 | }
27 |
28 | @SuppressWarnings("UnusedDeclaration")
29 | public SubstringArrayAdapter(Context context, int resource, int textViewResourceId, List objects) {
30 | super(context, resource, textViewResourceId, objects);
31 | this.original_objects = this.objects = objects;
32 | }
33 |
34 | @Override
35 | public Filter getFilter() {
36 | return myFilter;
37 | }
38 |
39 | private class SubStringFilter extends Filter {
40 | @Override
41 | protected FilterResults performFiltering(final CharSequence constraint) {
42 | final FilterResults results = new FilterResults();
43 | if ( constraint == null || constraint.length() == 0 ) {
44 | results.values = new ArrayList(original_objects);
45 | results.count = original_objects.size();
46 | return results;
47 | }
48 | final ArrayList matched = new ArrayList(
49 | Collections2.filter(original_objects, new Predicate() {
50 | @Override
51 | public boolean apply(String s) {
52 | return s.contains(constraint);
53 | }
54 | }));
55 | results.values = matched;
56 | results.count = matched.size();
57 | return results;
58 | }
59 |
60 | @SuppressWarnings("unchecked")
61 | @Override
62 | protected void publishResults(CharSequence constraint, FilterResults results) {
63 | objects = (List) results.values;
64 |
65 | if (results.count > 0)
66 | notifyDataSetChanged();
67 | else
68 | notifyDataSetInvalidated();
69 | }
70 | }
71 | @Override
72 | public int getCount() {
73 | return objects.size();
74 | }
75 |
76 | @Override
77 | public String getItem(int position) {
78 | return objects.get(position);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/layout/fragment_patterndata_detail.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
15 |
16 |
22 |
23 |
27 |
28 |
34 |
35 |
41 |
42 |
43 |
48 |
49 |
54 |
55 |
61 |
62 |
67 |
68 |
73 |
74 |
79 |
80 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/PatternDataDetailActivity.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.app.ActionBar;
4 | import android.app.Activity;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.support.v4.app.NavUtils;
8 | import android.view.MenuItem;
9 | import com.google.common.base.Preconditions;
10 |
11 |
12 | /**
13 | * An activity representing a single PatternData detail screen. This
14 | * activity is only used on handset devices. On tablet-size devices,
15 | * item details are presented side-by-side with a list of items
16 | * in a {@link PatternDataListActivity}.
17 | *
18 | * This activity is mostly just a 'shell' activity containing nothing
19 | * more than a {@link PatternDataDetailFragment}.
20 | */
21 | public class PatternDataDetailActivity extends Activity {
22 |
23 | private String accountId;
24 |
25 | @Override
26 | protected void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | setContentView(R.layout.activity_patterndata_detail);
29 |
30 | // Show the Up button in the action bar.
31 | setDisplayHomeAsUpEnabled();
32 |
33 | // savedInstanceState is non-null when there is fragment state
34 | // saved from previous configurations of this activity
35 | // (e.g. when rotating the screen from portrait to landscape).
36 | // In this case, the fragment will automatically be re-added
37 | // to its container so we don't need to manually add it.
38 | // For more information, see the Fragments API guide at:
39 | //
40 | // http://developer.android.com/guide/components/fragments.html
41 | //
42 | if (savedInstanceState == null) {
43 | // Create the detail fragment and add it to the activity
44 | // using a fragment transaction.
45 | Bundle arguments = new Bundle();
46 | accountId = getIntent().getStringExtra(PatternDataDetailFragment.ARG_ITEM_ID);
47 | Preconditions.checkNotNull(accountId, "%s was not set in PatternDatDetailActivity: %s",
48 | PatternDataDetailFragment.ARG_ITEM_ID, accountId);
49 | arguments.putString(PatternDataDetailFragment.ARG_ITEM_ID,
50 | getIntent().getStringExtra(PatternDataDetailFragment.ARG_ITEM_ID));
51 | arguments.putBoolean(PatternDataDetailFragment.ARG_TWO_PANE_MODE, false);
52 | arguments.putInt(PatternDataDetailFragment.ARG_PATTERN_POSITION,
53 | getIntent().getIntExtra(PatternDataDetailFragment.ARG_PATTERN_POSITION, 0));
54 | PatternDataDetailFragment fragment = new PatternDataDetailFragment();
55 | fragment.setArguments(arguments);
56 | getFragmentManager().beginTransaction()
57 | .add(R.id.patterndata_detail_container, fragment)
58 | .commit();
59 | }
60 | }
61 |
62 | @Override
63 | public boolean onOptionsItemSelected(MenuItem item) {
64 | int id = item.getItemId();
65 | if (id == android.R.id.home) {
66 | navigateUp();
67 | return true;
68 | }
69 | return super.onOptionsItemSelected(item);
70 | }
71 |
72 | private void setDisplayHomeAsUpEnabled() {
73 | // prevent the possible nullpointer if getActionBar returns null.
74 | ActionBar actionBar = getActionBar();
75 | if ( actionBar != null ) actionBar.setDisplayHomeAsUpEnabled(true);
76 | }
77 |
78 | public void navigateUp() {
79 |
80 | Intent intent = new Intent(this, PatternDataListActivity.class);
81 | intent.putExtra(PatternDataListFragment.ARG_ACCOUNT_ID, accountId);
82 | NavUtils.navigateUpTo(this, intent);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/AccountDetailActivity.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.app.ActionBar;
4 | import android.app.Activity;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.support.v4.app.NavUtils;
8 | import android.view.MenuItem;
9 |
10 | import java.util.ArrayList;
11 |
12 |
13 | /**
14 | * An activity representing a single Account detail screen. This
15 | * activity is only used on handset devices. On tablet-size devices,
16 | * item details are presented side-by-side with a list of items
17 | * in a {@link AccountListActivity}.
18 | *
19 | * This activity is mostly just a 'shell' activity containing nothing
20 | * more than a {@link AccountDetailFragment}.
21 | */
22 | public class AccountDetailActivity extends Activity {
23 | @SuppressWarnings("UnusedDeclaration")
24 | private static String LOG_TAG = Logtags.ACCOUNT_DETAIL_ACTIVITY.getTag();
25 |
26 | @Override
27 | protected void onCreate(Bundle savedInstanceState) {
28 | super.onCreate(savedInstanceState);
29 | setContentView(R.layout.activity_account_detail);
30 |
31 | String accId = getIntent().getStringExtra(AccountDetailFragment.ARG_ITEM_ID);
32 |
33 | // Show the Up button in the action bar.
34 | setDisplayHomeAsUpEnabled();
35 |
36 | // savedInstanceState is non-null when there is fragment state
37 | // saved from previous configurations of this activity
38 | // (e.g. when rotating the screen from portrait to landscape).
39 | // In this case, the fragment will automatically be re-added
40 | // to its container so we don't need to manually add it.
41 | // For more information, see the Fragments API guide at:
42 | //
43 | // http://developer.android.com/guide/components/fragments.html
44 | //
45 | if (savedInstanceState == null) {
46 | // Create the detail fragment and add it to the activity
47 | // using a fragment transaction.
48 | Bundle arguments = new Bundle();
49 | arguments.putString(AccountDetailFragment.ARG_ITEM_ID, accId);
50 | AccountDetailFragment fragment = new AccountDetailFragment();
51 | fragment.setArguments(arguments);
52 | getFragmentManager().beginTransaction()
53 | .add(R.id.account_detail_container, fragment)
54 | .commit();
55 | }
56 | }
57 |
58 | @Override
59 | public boolean onOptionsItemSelected(MenuItem item) {
60 | int id = item.getItemId();
61 | if (id == android.R.id.home) {
62 | // This ID represents the Home or Up button. In the case of this
63 | // activity, the Up button is shown. Use NavUtils to allow users
64 | // to navigate up one level in the application structure. For
65 | // more details, see the Navigation pattern on Android Design:
66 | //
67 | // http://developer.android.com/design/patterns/navigation.html#up-vs-back
68 | //
69 | Intent intent = new Intent(this, AccountListActivity.class);
70 | ArrayList accStack = getIntent().getStringArrayListExtra(AccountListFragment.STATE_ACCOUNT_STACK);
71 | if ( accStack != null && !accStack.isEmpty() )
72 | intent.putStringArrayListExtra(AccountListFragment.STATE_ACCOUNT_STACK, accStack);
73 | NavUtils.navigateUpTo(this, intent);
74 | return true;
75 | }
76 | return super.onOptionsItemSelected(item);
77 | }
78 |
79 | private void setDisplayHomeAsUpEnabled() {
80 | // prevent the possible nullpointer if getActionBar returns null.
81 | ActionBar actionBar = getActionBar();
82 | if ( actionBar != null ) actionBar.setDisplayHomeAsUpEnabled(true);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/AccountManagerSamples.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker;
2 |
3 | import com.google.common.collect.ImmutableList;
4 | import org.daveware.passwordmaker.*;
5 |
6 | @SuppressWarnings({"EmptyMethod", "UnusedParameters"})
7 | public class AccountManagerSamples {
8 |
9 | public static void addSamples(AccountManager accountManager) {
10 |
11 | }
12 |
13 | public static void addSamples2(AccountManager accountManager) {
14 | Database pwmProfiles = accountManager.getPwmProfiles();
15 | try {
16 | Account folder = new Account("Personal", true);
17 | folder.getChildren().add(new AccountBuilder().setAlgorithm(AlgorithmType.SHA256)
18 | .setName("Reddit")
19 | .setDesc("Reddit page")
20 | .setUrl("http://reddit.com")
21 | .setUsername("testUser1")
22 | .setCharacterSet(CharacterSets.BASE_93_SET)
23 | .setLength(20)
24 | .setPrefix("$r3d")
25 | .setModifier("1")
26 | .addWildcardPattern("reddit", "*://*.reddit.com/*")
27 | .addWildcardPattern("redditdomain", "*://reddit.com/*").build());
28 | folder.getChildren().add(new AccountBuilder().setAlgorithm(AlgorithmType.SHA256)
29 | .setName("facebook")
30 | .setDesc("facebook page")
31 | .setUrl("http://facebook.com")
32 | .setUsername("testUser2")
33 | .setCharacterSet(CharacterSets.ALPHANUMERIC)
34 | .setLength(23)
35 | .setSuffix("$f@d")
36 | .setModifier("2")
37 | .addWildcardPattern("facebook", "*://*.facebook.com/*")
38 | .addWildcardPattern("facebookdomain", "*://facebook.com/*").build());
39 | pwmProfiles.addAccount(pwmProfiles.getRootAccount(), folder);
40 | folder = new Account("Work", true);
41 | folder.getChildren().add(new AccountBuilder().setAlgorithm(AlgorithmType.RIPEMD160)
42 | .setName("stackoverflow")
43 | .setDesc("dev qa")
44 | .setUrl("http://stackoverflow.com")
45 | .setUsername("devuser1")
46 | .setCharacterSet(CharacterSets.ALPHANUMERIC)
47 | .setLength(23)
48 | .setPrefix("%D3v")
49 | .setModifier("3")
50 | .addWildcardPattern("stackoverflow", "*://*.stackoverflow.com/*")
51 | .addWildcardPattern("stackoverflowdomain", "*://stackoverflow.com/*").build());
52 | folder.getChildren().add(new AccountBuilder().setAlgorithm(AlgorithmType.SHA1)
53 | .setName("github.com")
54 | .setDesc("code repo")
55 | .setUrl("http://github.com")
56 | .setUsername("devuser2")
57 | .setCharacterSet(CharacterSets.HEX)
58 | .setLength(23)
59 | .setPrefix("%c0D3")
60 | .setModifier("4")
61 | .addWildcardPattern("github", "*://*.github.com/*")
62 | .addWildcardPattern("githubdomain", "*://github.com/*").build());
63 | pwmProfiles.addAccount(pwmProfiles.getRootAccount(), folder);
64 | } catch (Exception e) {
65 | throw new RuntimeException(e);
66 | }
67 |
68 | accountManager.getFavoriteUrls().addAll(ImmutableList.builder()
69 | .add("http://reddit.com/")
70 | .add("http://realnews.com/")
71 | .add("http://google.com/")
72 | .add("http://goo.gl/")
73 | .add("http://markerisred.com/")
74 | .add("http://github.com/")
75 | .add("http://www.stackoverflow.com/")
76 | .add("https://www.facebook.com/")
77 | .add("https://gmail.com/")
78 | .build());
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
28 |
31 |
32 |
36 |
39 |
40 |
44 |
47 |
48 |
52 |
55 |
56 |
60 |
63 |
64 |
68 |
71 |
72 |
76 |
79 |
80 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/EditFavoritesActivity.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.DialogInterface;
5 | import android.os.Bundle;
6 | import android.support.v4.app.NavUtils;
7 | import android.support.v7.app.ActionBar;
8 | import android.support.v7.app.AppCompatActivity;
9 | import android.support.v7.widget.Toolbar;
10 | import android.view.Menu;
11 | import android.view.MenuItem;
12 | import android.view.View;
13 | import android.view.WindowManager;
14 | import android.widget.EditText;
15 |
16 |
17 | public class EditFavoritesActivity extends AppCompatActivity {
18 |
19 | @Override
20 | protected void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_edit_favorites);
23 | setSupportActionBar((Toolbar)findViewById(R.id.main_toolbar));
24 | // Show the Up button in the action bar.
25 | setDisplayHomeAsUpEnabled();
26 | }
27 |
28 | protected EditFavoritesFragment getAccountListFragment() {
29 | return ((EditFavoritesFragment) getFragmentManager().findFragmentById(R.id.edit_favorites));
30 | }
31 |
32 |
33 | @Override
34 | public boolean onCreateOptionsMenu(Menu menu) {
35 | // Inflate the menu; this adds items to the action bar if it is present.
36 | getMenuInflater().inflate(R.menu.edit_favorites, menu);
37 | return true;
38 | }
39 |
40 | @Override
41 | public boolean onOptionsItemSelected(MenuItem item) {
42 | // Handle action bar item clicks here. The action bar will
43 | // automatically handle clicks on the Home/Up button, so long
44 | // as you specify a parent activity in AndroidManifest.xml.
45 | int id = item.getItemId();
46 | if (id == R.id.action_favs_add) {
47 | newFavorite();
48 | return true;
49 | } else if ( id == android.R.id.home) {
50 | // This ID represents the Home or Up button. In the case of this
51 | // activity, the Up button is shown. Use NavUtils to allow users
52 | // to navigate up one level in the application structure. For
53 | // more details, see the Navigation pattern on Android Design:
54 | //
55 | // http://developer.android.com/design/patterns/navigation.html#up-vs-back
56 | //
57 | NavUtils.navigateUpFromSameTask(this);
58 | return true;
59 | }
60 | return super.onOptionsItemSelected(item);
61 | }
62 |
63 | protected void addItem(String title) {
64 | getAccountListFragment().addItem(title);
65 |
66 | }
67 |
68 | private void newFavorite() {
69 | AlertDialog.Builder builder = new AlertDialog.Builder(this);
70 | final EditText editView = new EditText(this);
71 | editView.setLines(1);
72 | editView.setMinimumWidth(200);
73 | builder.setView(editView);
74 | builder.setPositiveButton(R.string.AddFavorite,
75 | new DialogInterface.OnClickListener() {
76 | public void onClick(DialogInterface dialog, int which) {
77 | String newFav = editView.getText().toString();
78 | addItem(newFav);
79 | }
80 | });
81 | builder.setNegativeButton(R.string.Cancel, null);
82 | final AlertDialog alert = builder.create();
83 | editView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
84 | public void onFocusChange(View v, boolean hasFocus) {
85 | if (hasFocus) {
86 | alert.getWindow()
87 | .setSoftInputMode(
88 | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
89 | }
90 |
91 | }
92 | });
93 | builder.setCancelable(true);
94 | alert.show();
95 | }
96 |
97 | private void setDisplayHomeAsUpEnabled() {
98 | ActionBar actionBar = getSupportActionBar();
99 | if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/passwordmaker/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 |
4 | /*
5 | * Gets the version name from the latest Git tag
6 | *
7 | * Tag each release like: git tag -a v1.0
8 | * then Rebuild for a nice version number
9 | *
10 | */
11 | def generateVersionName = { ->
12 | def stdout = new ByteArrayOutputStream()
13 | exec {
14 | commandLine 'git', 'describe', '--tags'
15 | standardOutput = stdout
16 | }
17 | String output = stdout.toString()
18 | if (output.startsWith('v')) output = output[1..-1]
19 | output = output.trim()
20 | return output
21 | }
22 |
23 | def generateVersionCode = { ->
24 | def versions = generateVersionName().split("-", 4)
25 | def code = versions[0].replaceAll("-.*", "")
26 | .split("\\.").collect {
27 | it.padLeft(2, "0")
28 | }.join("")
29 | if ( versions.size() >= 2 && versions[1].isNumber() ) {
30 | // This is off of a release tag: '2.0.5' but has a couple of commits since: 2.0.5-4-g6af5805 we want the code to have '04' in this case.
31 | code += versions[1].padLeft(2, "0")
32 | } else if ( versions.size() >= 3 && versions[2].isNumber() ) {
33 | // This has a '-qualifier-##' e.g. '2.0.5-SNAPSHOT-1-g6af5805' we want the code to have '01' appended in this case.
34 | code += versions[2].padLeft(2, "0")
35 | } else if ( versions[-1] == "SNAPSHOT") {
36 | // The first snapshot
37 | code += "00"
38 | } else {
39 | // This is if we are on the release tag itself, lets force this to be the end of the revisions
40 | // example: '2.0.5' as the tag, we want to add 99 to the end, so that any revisions handed out during testing will be
41 | // less than this revision.
42 | code += "99"
43 | }
44 | return code.toInteger()
45 | }
46 |
47 | apply plugin: 'com.android.application'
48 |
49 | android {
50 | compileSdkVersion 28
51 | defaultConfig {
52 | applicationId "org.passwordmaker.android"
53 | minSdkVersion 15 //2018-12-08: 99% of android users are using Sdk Version 15+. Too lazy to deal with upgrading anyways.
54 | targetSdkVersion 28
55 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
56 | versionCode generateVersionCode()
57 | versionName generateVersionName()
58 | println "Building version ${getVersionName()} (${getVersionCode()})"
59 | }
60 | buildTypes {
61 | release {
62 | minifyEnabled false
63 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
64 | }
65 | }
66 |
67 | signingConfigs {
68 | release {
69 | def env = System.getenv()
70 | if (env['PASSWORDMAKER_KEYSTORE_FILE'] != null) {
71 | storeFile file(env['PASSWORDMAKER_KEYSTORE_FILE'])
72 | storePassword env['PASSWORDMAKER_KEYSTORE_PASSWORD']
73 | keyAlias env['PASSWORDMAKER_KEYSTORE_KEY_ALIAS']
74 | keyPassword env['PASSWORDMAKER_KEYSTORE_KEY_PASSWORD']
75 | println ("Using keystore file: ${file(env['PASSWORDMAKER_KEYSTORE_FILE'])}")
76 | } else {
77 | println ("##################")
78 | println ("No keystore for release set.")
79 | println ("Set PASSWORDMAKER_KEYSTORE_FILE, PASSWORDMAKER_KEYSTORE_PASSWORD,"
80 | + "PASSWORDMAKER_KEYSTORE_KEY_ALIAS and PASSWORDMAKER_KEYSTORE_KEY_PASSWORD")
81 | println ("##################")
82 | }
83 | }
84 | }
85 | }
86 |
87 | dependencies {
88 | implementation fileTree(dir: 'libs', include: ['*.jar'])
89 | // You must install or update the Support Repository through the SDK manager to use this dependency.
90 | implementation 'com.android.support:appcompat-v7:28.0.0'
91 | implementation 'com.android.support.constraint:constraint-layout:1.1.3'
92 | implementation 'com.android.support:design:28.0.0'
93 | implementation 'com.intellij:annotations:12.0@jar'
94 | implementation 'org.passwordmaker:passwordmaker-je-lib:0.9.10'
95 | implementation 'com.madgag.spongycastle:core:1.50.0.0'
96 | implementation 'com.madgag.spongycastle:prov:1.50.0.0'
97 | implementation 'com.google.guava:guava:27.0.1-android'
98 | testImplementation 'junit:junit:4.12'
99 | }
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/ImportExportRdf.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.text.ClipboardManager;
6 | import android.util.Log;
7 | import android.view.View;
8 | import android.widget.Button;
9 | import android.widget.CheckBox;
10 | import android.widget.TextView;
11 | import android.widget.Toast;
12 | import com.google.common.base.Function;
13 | import com.google.common.base.Joiner;
14 | import com.google.common.collect.Iterables;
15 | import org.daveware.passwordmaker.Database;
16 | import org.daveware.passwordmaker.IncompatibleException;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 |
22 | public class ImportExportRdf extends Activity {
23 | private static final String LOG_TAG = Logtags.IMPORT_EXPORT_RDF.getTag();
24 |
25 | @Override
26 | protected void onCreate(Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | setContentView(R.layout.activity_import_export_rdf);
29 | getImportButton().setOnClickListener(new View.OnClickListener() {
30 | @Override
31 | public void onClick(View v) {
32 | onImportClick();
33 | }
34 | });
35 | getExportButton().setOnClickListener(new View.OnClickListener() {
36 | @Override
37 | public void onClick(View v) {
38 | onExportClick();
39 | }
40 | });
41 | }
42 |
43 | @SuppressWarnings("deprecation")
44 | private void onExportClick() {
45 | try {
46 | String str = PwmApplication.getInstance().serializeSettingsWithOutMasterPassword();
47 | final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
48 | clipboard.setText(str);
49 | Toast.makeText(this, "Exported profiles to clipboard", Toast.LENGTH_SHORT).show();
50 | } catch (Exception e) {
51 | Log.e(LOG_TAG, "Error exporting database", e);
52 | Toast.makeText(this, "Error exporting to RDF", Toast.LENGTH_SHORT).show();
53 | }
54 | }
55 |
56 | @SuppressWarnings("deprecation")
57 | private void onImportClick() {
58 | final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
59 | if (!clipboard.hasText()) {
60 | Toast.makeText(this, "No text in clipboard to export", Toast.LENGTH_SHORT).show();
61 | return;
62 | }
63 | try {
64 | String str = clipboard.getText().toString();
65 | List errors = new ArrayList();
66 | Database db = PwmApplication.getInstance().deserializedSettings(str, convertIsChecked(), errors);
67 | PwmApplication.getInstance().getAccountManager().getPwmProfiles().swapAccounts(db);
68 | PwmApplication.getInstance().loadFavoritesFromGlobalSettings();
69 | String originalInstructions = getResources().getString(R.string.lblImportInstructions);
70 | if ( !errors.isEmpty()) {
71 | originalInstructions += "Accounts not imported: \n" + convertToString(errors);
72 | }
73 | getInstructionsView().setText(originalInstructions);
74 | Toast.makeText(this, "Successfully imported RDF from clipboard", Toast.LENGTH_SHORT).show();
75 | } catch (Exception e) {
76 | Log.e(LOG_TAG, "Error importing database", e);
77 | Toast.makeText(this, "Error importing RDF from clipboard", Toast.LENGTH_SHORT).show();
78 | }
79 | }
80 |
81 | protected TextView getInstructionsView() {
82 | return (TextView)findViewById(R.id.lblInstructions);
83 | }
84 |
85 | protected Button getImportButton() {
86 | return (Button)findViewById(R.id.btnImport);
87 | }
88 |
89 | protected Button getExportButton() {
90 | return (Button)findViewById(R.id.btnExport);
91 | }
92 |
93 | protected boolean convertIsChecked() {
94 | CheckBox chkBox = (CheckBox)findViewById(R.id.chkConvertBadAlgo);
95 | return chkBox.isChecked();
96 | }
97 |
98 | protected String convertToString(List errors) {
99 | Iterable errorStrs = Iterables.transform(errors, new Function() {
100 | @Override
101 | public String apply(IncompatibleException input) {
102 | String msg = input.getMessage();
103 | int loc = msg.indexOf(':', msg.indexOf(':') + 1);
104 | return msg.substring(0, loc);
105 | }
106 | });
107 | return Joiner.on("\n").join(errorStrs);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/xmlwrappers/AndroidXmlStreamWriter.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android.xmlwrappers;
2 |
3 | import org.daveware.passwordmaker.xmlwrappers.XmlIOException;
4 | import org.daveware.passwordmaker.xmlwrappers.XmlStreamWriter;
5 | import org.xmlpull.v1.XmlPullParserException;
6 | import org.xmlpull.v1.XmlPullParserFactory;
7 | import org.xmlpull.v1.XmlSerializer;
8 |
9 | import java.io.IOException;
10 | import java.io.Writer;
11 | import java.util.LinkedList;
12 |
13 | public class AndroidXmlStreamWriter implements XmlStreamWriter {
14 | private XmlSerializer serializer;
15 | private Writer writer;
16 |
17 | private static class Tag {
18 | final String name;
19 | final String namespace;
20 |
21 | public Tag(String name, String namespace) {
22 | this.name = name;
23 | this.namespace = namespace;
24 | }
25 | }
26 |
27 | private Tag rootTag = null;
28 |
29 | private final LinkedList tagStack = new LinkedList();
30 |
31 | protected Tag addTagToStack(String name, @SuppressWarnings("SameParameterValue") String namespace) throws IOException {
32 | Tag ret = new Tag(name, namespace);
33 | // Stupid XmlSerializer from xml pull does this in the different order as the javax.xml.stream does.
34 | // For namespace declarations, they must happen before the tag is added to the serializer.
35 | if ( tagStack.isEmpty() ) {
36 | if ( rootTag == null ) {
37 | rootTag = ret;
38 | return rootTag;
39 | } else {
40 | serializer.startTag(rootTag.namespace, rootTag.name);
41 | tagStack.push(rootTag);
42 | }
43 | }
44 | tagStack.push(ret);
45 | serializer.startTag(ret.namespace, ret.name);
46 | return ret;
47 | }
48 |
49 | protected void flushRootIfNeeded() throws IOException {
50 | if ( tagStack.isEmpty() && rootTag != null ) {
51 | serializer.startTag(rootTag.namespace, rootTag.name);
52 | tagStack.push(rootTag);
53 | }
54 | }
55 |
56 | protected Tag peekTagStack() {
57 | return tagStack.peek();
58 | }
59 |
60 | protected Tag popTagStack() {
61 | return tagStack.pop();
62 | }
63 |
64 | public AndroidXmlStreamWriter(Writer writer) throws XmlIOException {
65 | try {
66 | this.writer = writer;
67 | XmlPullParserFactory factory = XmlPullParserFactory.newInstance(
68 | System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
69 | if ( factory == null ) throw new XmlIOException("Can not create an XmlPullFactory");
70 | serializer = factory.newSerializer();
71 | serializer.setOutput(writer);
72 | } catch (XmlPullParserException e) {
73 | throw new XmlIOException(e);
74 | } catch (IOException e) {
75 | throw new XmlIOException(e);
76 | }
77 |
78 | }
79 |
80 | @Override
81 | public void writeStartDocument() throws XmlIOException {
82 | try {
83 | serializer.startDocument("UTF-8", null);
84 | } catch (IOException e) {
85 | throw new XmlIOException(e);
86 | }
87 | }
88 |
89 | @Override
90 | public void writeStartDocument(String encoding, String version) throws XmlIOException {
91 | try {
92 | serializer.startDocument(encoding, null);
93 | } catch (IOException e) {
94 | throw new XmlIOException(e);
95 | }
96 | }
97 |
98 | @Override
99 | public void addPrefix(String prefix, String namespace) throws XmlIOException {
100 | try {
101 | serializer.setPrefix(prefix, namespace);
102 | } catch (IOException e) {
103 | throw new XmlIOException(e);
104 | }
105 | }
106 |
107 |
108 | @Override
109 | public void writeStartElement(String name) throws XmlIOException {
110 | try {
111 | addTagToStack(name, "");
112 | } catch (IOException e) {
113 | throw new XmlIOException(e);
114 | }
115 | }
116 |
117 | @Override
118 | public void writeAttribute(String localName, String value) throws XmlIOException {
119 | try {
120 | // alright, if we are asking to write to an attribute we must be done with writing any namespaces.
121 | flushRootIfNeeded();
122 | serializer.attribute("", localName, value);
123 | } catch (IOException e) {
124 | throw new XmlIOException(e);
125 | }
126 | }
127 |
128 | @Override
129 | public void writeEndElement() throws XmlIOException {
130 | try {
131 | // if we ask to end our element, and we haven't done anything with the root, let flush that first
132 | flushRootIfNeeded();
133 | Tag lastTag = peekTagStack();
134 | serializer.endTag(lastTag.namespace, lastTag.name);
135 | popTagStack(); // only pop if no exception was thrown, this way we can debug.
136 | } catch (IOException e) {
137 | throw new XmlIOException(e);
138 | }
139 | }
140 |
141 | @Override
142 | public void writeEndDocument() throws XmlIOException {
143 | try {
144 | serializer.endDocument();
145 | } catch (IOException e) {
146 | throw new XmlIOException(e);
147 | }
148 | }
149 |
150 | @Override
151 | public void flush() throws XmlIOException {
152 | try {
153 | serializer.flush();
154 | } catch (IOException e) {
155 | throw new XmlIOException(e);
156 | }
157 | }
158 |
159 | @Override
160 | public void close() throws XmlIOException {
161 | try {
162 | writer.close();
163 | } catch (IOException e) {
164 | throw new XmlIOException(e);
165 | }
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
14 |
18 |
19 |
25 |
26 |
27 |
28 |
34 |
35 |
38 |
43 |
44 |
45 |
48 |
50 |
53 |
57 |
58 |
64 |
65 |
71 |
72 |
75 |
80 |
81 |
87 |
88 |
98 |
99 |
104 |
105 |
112 |
113 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/PatternDataListActivity.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.app.ActionBar;
4 | import android.app.Activity;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.support.v4.app.NavUtils;
8 | import android.view.Menu;
9 | import android.view.MenuItem;
10 | import org.daveware.passwordmaker.AccountPatternData;
11 |
12 |
13 | /**
14 | * An activity representing a list of PatternData. This activity
15 | * has different presentations for handset and tablet-size devices. On
16 | * handsets, the activity presents a list of items, which when touched,
17 | * lead to a {@link PatternDataDetailActivity} representing
18 | * item details. On tablets, the activity presents the list of items and
19 | * item details side-by-side using two vertical panes.
20 | *
21 | * The activity makes heavy use of fragments. The list of items is a
22 | * {@link PatternDataListFragment} and the item details
23 | * (if present) is a {@link PatternDataDetailFragment}.
24 | *
25 | * This activity also implements the required
26 | * {@link PatternDataListFragment.Callbacks} interface
27 | * to listen for item selections.
28 | */
29 | public class PatternDataListActivity extends Activity
30 | implements PatternDataListFragment.Callbacks {
31 | @SuppressWarnings("UnusedDeclaration")
32 | private static final String LOG_TAG = Logtags.PATTERN_DATA_LIST_ACTIVITY.getTag();
33 | /**
34 | * Whether or not the activity is in two-pane mode, i.e. running on a tablet
35 | * device.
36 | */
37 | private boolean mTwoPane;
38 | private String accountId;
39 |
40 | @Override
41 | protected void onCreate(Bundle savedInstanceState) {
42 | super.onCreate(savedInstanceState);
43 | setContentView(R.layout.activity_patterndata_list);
44 | // Show the Up button in the action bar.
45 | setDisplayHomeAsUpEnabled();
46 |
47 | if (findViewById(R.id.patterndata_detail_container) != null) {
48 | // The detail container view will be present only in the
49 | // large-screen layouts (res/values-large and
50 | // res/values-sw600dp). If this view is present, then the
51 | // activity should be in two-pane mode.
52 | mTwoPane = true;
53 |
54 | // In two-pane mode, list items should be given the
55 | // 'activated' state when touched.
56 | getListFragment().setActivateOnItemClick();
57 | }
58 | }
59 |
60 | @Override
61 | protected void onResume() {
62 | super.onResume();
63 | accountId = getIntent().getStringExtra(PatternDataListFragment.ARG_ACCOUNT_ID);
64 | getListFragment().setAccountId(accountId);
65 | }
66 |
67 | protected PatternDataListFragment getListFragment() {
68 | return ((PatternDataListFragment) getFragmentManager()
69 | .findFragmentById(R.id.patterndata_list));
70 | }
71 |
72 | @Override
73 | public boolean onCreateOptionsMenu(Menu menu) {
74 | // Inflate the menu; this adds items to the action bar if it is present.
75 | getMenuInflater().inflate(R.menu.pattern_data_list, menu);
76 | return true;
77 | }
78 |
79 | @Override
80 | public boolean onOptionsItemSelected(MenuItem item) {
81 | int id = item.getItemId();
82 | if (id == R.id.action_pattern_add) {
83 | getListFragment().createNewPattern();
84 | return true;
85 | } else if (id == android.R.id.home) {
86 | nagivateUp();
87 | return true;
88 | }
89 | return super.onOptionsItemSelected(item);
90 | }
91 |
92 | /**
93 | * Callback method from {@link PatternDataListFragment.Callbacks}
94 | * indicating that the item with the given ID was selected.
95 | */
96 | @Override
97 | public void onItemSelected(int position, AccountPatternData patternData) {
98 | if (mTwoPane) {
99 | // In two-pane mode, show the detail view in this activity by
100 | // adding or replacing the detail fragment using a
101 | // fragment transaction.
102 | Bundle arguments = new Bundle();
103 | arguments.putString(PatternDataDetailFragment.ARG_ITEM_ID, accountId);
104 | arguments.putBoolean(PatternDataDetailFragment.ARG_TWO_PANE_MODE, mTwoPane);
105 | arguments.putInt(PatternDataDetailFragment.ARG_PATTERN_POSITION, position);
106 | PatternDataDetailFragment fragment = new PatternDataDetailFragment();
107 | fragment.setArguments(arguments);
108 | getFragmentManager().beginTransaction()
109 | .replace(R.id.patterndata_detail_container, fragment)
110 | .commit();
111 |
112 | } else {
113 | // In single-pane mode, simply start the detail activity
114 | // for the selected item ID.
115 | Intent detailIntent = new Intent(this, PatternDataDetailActivity.class);
116 | detailIntent.putExtra(PatternDataDetailFragment.ARG_ITEM_ID, accountId);
117 | detailIntent.putExtra(PatternDataDetailFragment.ARG_TWO_PANE_MODE, mTwoPane);
118 | detailIntent.putExtra(PatternDataDetailFragment.ARG_PATTERN_POSITION, position);
119 | startActivity(detailIntent);
120 | }
121 | }
122 |
123 | private void nagivateUp() {
124 | Intent intent;
125 | if ( mTwoPane ) {
126 | intent = new Intent(this, AccountListActivity.class);
127 |
128 | } else {
129 | intent = new Intent(this, AccountDetailActivity.class);
130 | }
131 | if (accountId != null) {
132 | intent.putExtra(AccountDetailFragment.ARG_ITEM_ID, accountId);
133 | }
134 | NavUtils.navigateUpTo(this, intent);
135 | }
136 |
137 | private void setDisplayHomeAsUpEnabled() {
138 | // prevent the possible nullpointer if getActionBar returns null.
139 | ActionBar actionBar = getActionBar();
140 | if ( actionBar != null ) actionBar.setDisplayHomeAsUpEnabled(true);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/PatternDataDetailFragment.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.app.Fragment;
4 | import android.os.Bundle;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.Button;
9 | import android.widget.CheckBox;
10 | import android.widget.RadioButton;
11 | import android.widget.TextView;
12 | import org.daveware.passwordmaker.Account;
13 | import org.daveware.passwordmaker.AccountManager;
14 | import org.daveware.passwordmaker.AccountPatternData;
15 | import org.daveware.passwordmaker.AccountPatternType;
16 | import org.jetbrains.annotations.NotNull;
17 |
18 | /**
19 | * A fragment representing a single PatternData detail screen.
20 | * This fragment is either contained in a {@link PatternDataListActivity}
21 | * in two-pane mode (on tablets) or a {@link PatternDataDetailActivity}
22 | * on handsets.
23 | */
24 | @SuppressWarnings("ConstantConditions")
25 | public class PatternDataDetailFragment extends Fragment {
26 | /**
27 | * The fragment argument representing the item ID that this fragment
28 | * represents.
29 | */
30 | public static final String ARG_ITEM_ID = "item_id";
31 | public static final String ARG_PATTERN_POSITION = "pattern_position";
32 | public static final String ARG_TWO_PANE_MODE = "two_pane_mode";
33 |
34 | @SuppressWarnings("FieldCanBeLocal")
35 | private Account account;
36 | private AccountPatternData patternData;
37 | private boolean twoPaneMode;
38 |
39 | /**
40 | * Mandatory empty constructor for the fragment manager to instantiate the
41 | * fragment (e.g. upon screen orientation changes).
42 | */
43 | public PatternDataDetailFragment() {
44 | }
45 |
46 | @Override
47 | public void onCreate(Bundle savedInstanceState) {
48 | super.onCreate(savedInstanceState);
49 |
50 | if (getArguments().containsKey(ARG_ITEM_ID)) {
51 | // Load the dummy content specified by the fragment
52 | // arguments. In a real-world scenario, use a Loader
53 | // to load content from a content provider.
54 | String itemId = getArguments().getString(ARG_ITEM_ID);
55 | int patternPosition = getArguments().getInt(ARG_PATTERN_POSITION, 0);
56 | AccountManager accountManager = PwmApplication.getInstance().getAccountManager();
57 | account = accountManager.getPwmProfiles().findAccountById(itemId);
58 | patternData = account.getPatterns().get(patternPosition);
59 | twoPaneMode = getArguments().getBoolean(ARG_TWO_PANE_MODE, false);
60 | }
61 | }
62 |
63 | @Override
64 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
65 | Bundle savedInstanceState) {
66 | return inflater.inflate(R.layout.fragment_patterndata_detail, container, false);
67 | }
68 |
69 | @Override
70 | public void onViewCreated(View view, Bundle savedInstanceState) {
71 | super.onViewCreated(view, savedInstanceState);
72 |
73 | // Show the dummy content as text in a TextView.
74 | if (patternData != null) {
75 | setPatternData(patternData);
76 | }
77 | getPrimaryButton().setOnClickListener(new View.OnClickListener() {
78 | @Override
79 | public void onClick(View v) {
80 | savePatternData();
81 | if ( ! twoPaneMode )
82 | ((PatternDataDetailActivity)getActivity()).navigateUp();
83 | }
84 | });
85 |
86 | getDomainRegexButton().setOnClickListener(new View.OnClickListener() {
87 | @Override
88 | public void onClick(View v) {
89 | setDomainPattern();
90 | }
91 | });
92 |
93 | if ( ! twoPaneMode ) {
94 | getCancelButton().setOnClickListener(new View.OnClickListener() {
95 | @Override
96 | public void onClick(View v) {
97 | ((PatternDataDetailActivity)getActivity()).navigateUp();
98 | }
99 | });
100 | } else {
101 | // two pane mode won't have this since you can just change views later
102 | getCancelButton().setVisibility(View.INVISIBLE);
103 | }
104 | }
105 |
106 | protected void setPatternData(@NotNull AccountPatternData pd) {
107 | patternData = pd;
108 | getTextDescription().setText(pd.getDesc());
109 | getTextPattern().setText(pd.getPattern());
110 | setPatternType(pd.getType());
111 | getCheckEnabled().setChecked(pd.isEnabled());
112 | }
113 |
114 | protected void savePatternData() {
115 | patternData.setDesc(getTextDescription().getText().toString());
116 | patternData.setPattern(getTextPattern().getText().toString());
117 | patternData.setType(getPatternType());
118 | patternData.setEnabled(getCheckEnabled().isChecked());
119 | }
120 |
121 | protected TextView getTextDescription() {
122 | return (TextView)getView().findViewById(R.id.txtPatternDesc);
123 | }
124 |
125 | protected TextView getTextPattern() {
126 | return (TextView)getView().findViewById(R.id.txtPatternExpression);
127 | }
128 |
129 | protected CheckBox getCheckEnabled() {
130 | return (CheckBox)getView().findViewById(R.id.chkEnabled);
131 | }
132 |
133 | protected Button getPrimaryButton() {
134 | return (Button)getView().findViewById(R.id.primary);
135 | }
136 |
137 | protected Button getCancelButton() {
138 | return (Button)getView().findViewById(android.R.id.closeButton);
139 | }
140 |
141 | protected RadioButton getOptionWildcard() {
142 | return (RadioButton)getView().findViewById(R.id.optWildcard);
143 | }
144 |
145 | protected Button getDomainRegexButton() {
146 | return (Button)getView().findViewById(R.id.btnDomainRegex);
147 | }
148 |
149 | @SuppressWarnings("UnusedDeclaration")
150 | protected RadioButton getOptionRegex() {
151 | return (RadioButton)getView().findViewById(R.id.optRegex);
152 | }
153 |
154 | protected AccountPatternType getPatternType() {
155 | if ( getOptionWildcard().isChecked() ) {
156 | return AccountPatternType.WILDCARD;
157 | } else {
158 | return AccountPatternType.REGEX;
159 | }
160 | }
161 |
162 | protected void setPatternType(AccountPatternType type) {
163 | boolean isWildcard = type == AccountPatternType.WILDCARD;
164 | getOptionWildcard().setChecked(isWildcard);
165 | getOptionWildcard().setChecked(!isWildcard);
166 | }
167 |
168 | protected void setDomainPattern() {
169 | getTextPattern().setText("(.*://)?(.*\\.)?domain\\.com(/.*)?");
170 | getOptionRegex().setChecked(true);
171 | }
172 |
173 | }
174 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/layout/fragment_account_detail.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
55 |
56 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PasswordMaker Pro
5 | PWMP
6 | Password
7 | Master Password
8 | Master Password Verification Code
9 | Input Text
10 | Character Set
11 | Copy
12 | Save
13 | Create
14 | Cancel
15 | Protocol
16 | Subdomain
17 | Port, path, anchor, query parameters
18 | Add new
19 |
20 |
21 | Not At All
22 | Before Generating Password
23 | After Generating Password
24 | Before And After Generating Password
25 |
26 |
27 |
28 | MD4
29 | HMAC MD4
30 | MD5
31 | MD5 V 0.6
32 | HMAC MD5
33 | HMAC MD5 V 0.6
34 | SHA 1
35 | HMAC SHA 1
36 | SHA 256
37 | HMAC SHA 256
38 | RIPEMD 160
39 | HMAC RIPEMD 160
40 |
41 |
42 | Level 1
43 | Level 2
44 | Level 3
45 | Level 4
46 | Level 5
47 | Level 6
48 | Level 7
49 | Level 8
50 | Level 9
51 |
52 |
53 | Password Length
54 |
55 |
56 | Alpha Number Symbols
57 | Alpha Num
58 | Hex number
59 | Number
60 | Alpha
61 | Symbols
62 | Custom (Enter your own)
63 |
64 |
65 | Prefix
66 | Suffix
67 | Profile:
68 | Name
69 | URL Parts to use
70 | Use leet
71 | Leet Level
72 | Username
73 | Modifier
74 | Domain
75 | Save Input
76 | Hash Algo
77 | Profiles
78 | Favorites
79 | Profiles
80 | Profile Detail
81 | Edit Favorites
82 | Add
83 | Confirm Delete
84 | Delete
85 | PatternData
86 | PatternData Detail
87 | Description
88 | Wildcard
89 | Regular Expression
90 | Expression
91 | Enabled
92 | Show Patterns
93 | ImportExportRdf
94 | Import
95 | Export
96 | The import process is destructive. It will replace any account already in Passwordmaker. To import first copy the RDF document to your clipboard. Then click the Import button below.
97 | To Export, click the export button. It will then copy the RDF document to the clipboard.
98 | Import/Export
99 | add
100 | Add
101 | Use Text
102 | The javascript/firefox versions contains \'HMAC-SHA-256 V 1.5.1\' which was a buggy implementation of the hash algo. The JVM does not support this, and the developer for the android version is too lazy to implement it himself. You can try to \'convert\' to the nearest algo type, but it may not generate the same password always.
103 | Create Folder
104 | password
105 | confirm password
106 | Domain Regex
107 | Delete pattern
108 | Convert Buggy Javascript Algo
109 | Clear
110 | Delete
111 | Use
112 |
113 |
114 | Never
115 | 1 minute
116 | 5 minute
117 | 10 minutes
118 | 15 minutes
119 | Forever
120 |
121 |
122 | 0
123 | 30
124 | 60
125 | 300
126 | 600
127 | 900
128 | 31557600
129 |
130 |
131 | 99999999
132 | Set the master password hash to verify password.
133 | Show username for accounts
134 | Show password strength
135 | Settings
136 | Notes
137 | 0
138 | View Account
139 | View
140 | Auto Add Input to Favorites
141 |
142 |
143 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/PatternDataListFragment.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.app.Activity;
4 | import android.app.AlertDialog;
5 | import android.app.ListFragment;
6 | import android.content.DialogInterface;
7 | import android.os.Bundle;
8 | import android.view.View;
9 | import android.widget.ArrayAdapter;
10 | import android.widget.ListView;
11 | import com.google.common.base.Preconditions;
12 | import org.daveware.passwordmaker.Account;
13 | import org.daveware.passwordmaker.AccountPatternData;
14 | import org.jetbrains.annotations.NotNull;
15 | import org.passwordmaker.android.widgets.SwipeDismissListViewTouchListener;
16 |
17 | /**
18 | * A list fragment representing a list of PatternData. This fragment
19 | * also supports tablet devices by allowing list items to be given an
20 | * 'activated' state upon selection. This helps indicate which item is
21 | * currently being viewed in a {@link PatternDataDetailFragment}.
22 | *
23 | * Activities containing this fragment MUST implement the {@link Callbacks}
24 | * interface.
25 | */
26 | public class PatternDataListFragment extends ListFragment
27 | implements SwipeDismissListViewTouchListener.DismissCallbacks {
28 |
29 | /**
30 | * The serialization (saved instance state) Bundle key representing the
31 | * activated item position. Only used on tablets.
32 | */
33 | private static final String STATE_ACTIVATED_POSITION = "activated_position";
34 | public static final String ARG_ACCOUNT_ID = "account_id";
35 |
36 | /**
37 | * The fragment's current callback object, which is notified of list item
38 | * clicks.
39 | */
40 | private Callbacks mCallbacks = sDummyCallbacks;
41 |
42 | /**
43 | * The current activated item position. Only used on tablets.
44 | */
45 | private int mActivatedPosition = ListView.INVALID_POSITION;
46 | @SuppressWarnings("FieldCanBeLocal")
47 | private Account account;
48 | // We need to keep a reference to this
49 | @SuppressWarnings("FieldCanBeLocal")
50 | private SwipeDismissListViewTouchListener touchListener;
51 |
52 | public void setAccountId(@NotNull String accountId) {
53 | account = PwmApplication.getInstance().getAccountManager().getPwmProfiles().findAccountById(accountId);
54 | Preconditions.checkNotNull(account, "Can not find account by id: %s", accountId);
55 | setListAdapter(new ArrayAdapter(
56 | getActivity(),
57 | android.R.layout.simple_list_item_activated_1,
58 | android.R.id.text1, account.getPatterns()));
59 | }
60 |
61 | /**
62 | * A callback interface that all activities containing this fragment must
63 | * implement. This mechanism allows activities to be notified of item
64 | * selections.
65 | */
66 | public interface Callbacks {
67 | /**
68 | * Callback for when an item has been selected.
69 | */
70 | public void onItemSelected(int position, AccountPatternData patternData);
71 | }
72 |
73 | /**
74 | * A dummy implementation of the {@link Callbacks} interface that does
75 | * nothing. Used only when this fragment is not attached to an activity.
76 | */
77 | private final static Callbacks sDummyCallbacks = new Callbacks() {
78 | @Override
79 | public void onItemSelected(int position, AccountPatternData patternData) {
80 | }
81 | };
82 |
83 | /**
84 | * Mandatory empty constructor for the fragment manager to instantiate the
85 | * fragment (e.g. upon screen orientation changes).
86 | */
87 | public PatternDataListFragment() {
88 | }
89 |
90 | @Override
91 | public void onViewCreated(View view, Bundle savedInstanceState) {
92 | super.onViewCreated(view, savedInstanceState);
93 |
94 | // Restore the previously serialized activated item position.
95 | if (savedInstanceState != null
96 | && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
97 | setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
98 | }
99 | }
100 |
101 | @Override
102 | public void onAttach(Activity activity) {
103 | super.onAttach(activity);
104 |
105 | // Activities containing this fragment must implement its callbacks.
106 | if (!(activity instanceof Callbacks)) {
107 | throw new IllegalStateException("Activity must implement fragment's callbacks.");
108 | }
109 |
110 | if ( getArguments() != null ) {
111 | String accountId = getArguments().getString(ARG_ACCOUNT_ID);
112 | setAccountId(accountId);
113 | }
114 |
115 | mCallbacks = (Callbacks) activity;
116 | }
117 |
118 | @Override
119 | public void onDetach() {
120 | super.onDetach();
121 |
122 | // Reset the active callbacks interface to the dummy implementation.
123 | mCallbacks = sDummyCallbacks;
124 | }
125 |
126 | @Override
127 | public void onListItemClick(ListView listView, View view, int position, long id) {
128 | super.onListItemClick(listView, view, position, id);
129 | mActivatedPosition = position;
130 | mCallbacks.onItemSelected(position, getPatternAdapter().getItem(position));
131 | }
132 |
133 | @Override
134 | public void onSaveInstanceState(Bundle outState) {
135 | super.onSaveInstanceState(outState);
136 | if (mActivatedPosition != ListView.INVALID_POSITION) {
137 | // Serialize and persist the activated item position.
138 | outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
139 | }
140 | }
141 |
142 | @Override
143 | public void onStart() {
144 | super.onStart();
145 | ListView listView = getListView();
146 | touchListener = new SwipeDismissListViewTouchListener(listView, this);
147 | listView.setOnTouchListener(touchListener);
148 | listView.setOnScrollListener(touchListener.makeScrollListener());
149 | }
150 |
151 | public void createNewPattern() {
152 | getPatternAdapter().add(new AccountPatternData());
153 | int position = getPatternAdapter().getCount()-1;
154 | setActivatedPosition(position);
155 | mCallbacks.onItemSelected(position, getPatternAdapter().getItem(position));
156 | }
157 |
158 | protected ArrayAdapter getPatternAdapter() {
159 |
160 | @SuppressWarnings("unchecked")
161 | ArrayAdapter result = (ArrayAdapter)getListAdapter();
162 | return result;
163 | }
164 |
165 | /**
166 | * Turns on activate-on-click mode. When this mode is on, list items will be
167 | * given the 'activated' state when touched.
168 | */
169 | public void setActivateOnItemClick() {
170 | // When setting CHOICE_MODE_SINGLE, ListView will automatically
171 | // give items the 'activated' state when touched.
172 | getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
173 | }
174 |
175 | @Override
176 | public boolean canDismiss(int position) {
177 | return true;
178 | }
179 |
180 | @Override
181 | public void onDismiss(ListView listView, int[] reverseSortedPositions) {
182 | if ( reverseSortedPositions.length != 1 ) return;
183 | int position = reverseSortedPositions[0];
184 | confirmDelete(position);
185 | }
186 |
187 | protected void reallyDelete(final int position) {
188 | ArrayAdapter patterns = getPatternAdapter();
189 | patterns.remove(patterns.getItem(position));
190 | patterns.notifyDataSetChanged();
191 | }
192 |
193 | protected void confirmDelete(final int position) {
194 | final ArrayAdapter patterns = getPatternAdapter();
195 | AccountPatternData pattern = patterns.getItem(position);
196 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
197 | .setIcon(android.R.drawable.ic_dialog_alert)
198 | .setTitle(R.string.confirm_delete)
199 | .setMessage(getResources().getText(R.string.delete_confirmation_text) + " '" + pattern.getDesc() + "'")
200 | .setCancelable(true);
201 | builder.setPositiveButton(R.string.delete_button, new DialogInterface.OnClickListener() {
202 | @Override
203 | public void onClick(DialogInterface dialog, int which) {
204 | reallyDelete(position);
205 | }
206 | });
207 | builder.setNegativeButton(R.string.Cancel, null);
208 | final AlertDialog alert = builder.create();
209 | alert.show();
210 | }
211 |
212 | private void setActivatedPosition(int position) {
213 | if (position == ListView.INVALID_POSITION) {
214 | getListView().setItemChecked(mActivatedPosition, false);
215 | } else {
216 | getListView().setItemChecked(position, true);
217 | }
218 |
219 | mActivatedPosition = position;
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/AccountListActivity.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.app.AlertDialog;
4 | import android.content.DialogInterface;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.support.v4.app.NavUtils;
8 | import android.support.v7.app.ActionBar;
9 | import android.support.v7.app.AppCompatActivity;
10 | import android.support.v7.widget.Toolbar;
11 | import android.util.Log;
12 | import android.view.Menu;
13 | import android.view.MenuItem;
14 | import android.view.View;
15 | import android.view.WindowManager;
16 | import android.widget.EditText;
17 | import android.widget.Toast;
18 |
19 | import org.daveware.passwordmaker.Account;
20 | import org.jetbrains.annotations.NotNull;
21 |
22 |
23 | /**
24 | * An activity representing a list of Accounts. This activity
25 | * has different presentations for handset and tablet-size devices. On
26 | * handsets, the activity presents a list of items, which when touched,
27 | * lead to a {@link AccountDetailActivity} representing
28 | * item details. On tablets, the activity presents the list of items and
29 | * item details side-by-side using two vertical panes.
30 | *
31 | * The activity makes heavy use of fragments. The list of items is a
32 | * {@link AccountListFragment} and the item details
33 | * (if present) is a {@link AccountDetailFragment}.
34 | *
35 | * This activity also implements the required
36 | * {@link AccountListFragment.Callbacks} interface
37 | * to listen for item selections.
38 | */
39 | public class AccountListActivity extends AppCompatActivity
40 | implements AccountListFragment.Callbacks {
41 |
42 |
43 | private static final String LOG_TAG = Logtags.ACCOUNT_LIST_ACTIVITY.getTag();
44 | /**
45 | * Whether or not the activity is in two-pane mode, i.e. running on a tablet
46 | * device.
47 | */
48 | private boolean mTwoPane;
49 |
50 | @Override
51 | protected void onCreate(Bundle savedInstanceState) {
52 | super.onCreate(savedInstanceState);
53 | setContentView(R.layout.activity_account_list);
54 | setSupportActionBar((Toolbar)findViewById(R.id.main_toolbar));
55 | setDisplayHomeAsUpEnabled();
56 |
57 | if (findViewById(R.id.account_detail_container) != null) {
58 | // The detail container view will be present only in the
59 | // large-screen layouts (res/values-large and
60 | // res/values-sw600dp). If this view is present, then the
61 | // activity should be in two-pane mode.
62 | mTwoPane = true;
63 |
64 | // In two-pane mode, list items should be given the
65 | // 'activated' state when touched.
66 | getAccountListFragment().setActivateOnItemClick();
67 | }
68 | // TODO: If exposing deep links into your app, handle intents here.
69 | }
70 |
71 | protected AccountListFragment getAccountListFragment() {
72 | return ((AccountListFragment) getFragmentManager().findFragmentById(R.id.fragment_account_list));
73 | }
74 |
75 | @Override
76 | public boolean onCreateOptionsMenu(Menu menu) {
77 | // Inflate the menu; this adds items to the action bar if it is present.
78 | getMenuInflater().inflate(R.menu.account_list, menu);
79 | return true;
80 | }
81 |
82 | @Override
83 | public boolean onOptionsItemSelected(MenuItem item) {
84 | int id = item.getItemId();
85 | if (id == R.id.action_account_add) {
86 | addNewAccount();
87 | return true;
88 | } else if ( id == R.id.action_account_folder_add ) {
89 | addNewFolder();
90 | return true;
91 | } else if (id == android.R.id.home) {
92 | // This ID represents the Home or Up button. In the case of this
93 | // activity, the Up button is shown. Use NavUtils to allow users
94 | // to navigate up one level in the application structure. For
95 | // more details, see the Navigation pattern on Android Design:
96 | //
97 | // http://developer.android.com/design/patterns/navigation.html#up-vs-back
98 | //
99 | NavUtils.navigateUpFromSameTask(this);
100 | return true;
101 | }
102 | return super.onOptionsItemSelected(item);
103 | }
104 |
105 | private void addNewAccount(String accountName) {
106 | getAccountListFragment().createNewAccount(accountName);
107 | }
108 |
109 | private void addNewAccount() {
110 | AlertDialog.Builder builder = new AlertDialog.Builder(this);
111 | final EditText editView = new EditText(this);
112 | editView.setLines(1);
113 | editView.setMinimumWidth(200);
114 | builder.setView(editView);
115 | builder.setPositiveButton(R.string.AddProfile,
116 | new DialogInterface.OnClickListener() {
117 | public void onClick(DialogInterface dialog, int which) {
118 | String newProfile = editView.getText().toString();
119 | addNewAccount(newProfile);
120 | }
121 | });
122 | builder.setNegativeButton(R.string.Cancel, null);
123 | final AlertDialog alert = builder.create();
124 | editView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
125 | public void onFocusChange(View v, boolean hasFocus) {
126 | if (hasFocus) {
127 | alert.getWindow()
128 | .setSoftInputMode(
129 | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
130 | }
131 |
132 | }
133 | });
134 | builder.setCancelable(true);
135 | alert.show();
136 | }
137 |
138 | private void addNewFolder(String folderName) {
139 | getAccountListFragment().createNewFolder(folderName);
140 | }
141 |
142 | private void addNewFolder() {
143 | AlertDialog.Builder builder = new AlertDialog.Builder(this);
144 | final EditText editView = new EditText(this);
145 | editView.setLines(1);
146 | editView.setMinimumWidth(200);
147 | builder.setView(editView);
148 | builder.setPositiveButton(R.string.AddProfile,
149 | new DialogInterface.OnClickListener() {
150 | public void onClick(DialogInterface dialog, int which) {
151 | String newFolder = editView.getText().toString();
152 | addNewFolder(newFolder);
153 | }
154 | });
155 | builder.setNegativeButton(R.string.Cancel, null);
156 | final AlertDialog alert = builder.create();
157 | editView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
158 | public void onFocusChange(View v, boolean hasFocus) {
159 | if (hasFocus) {
160 | alert.getWindow()
161 | .setSoftInputMode(
162 | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
163 | }
164 |
165 | }
166 | });
167 | builder.setCancelable(true);
168 | alert.show();
169 | }
170 |
171 | /**
172 | * Callback method from {@link AccountListFragment.Callbacks}
173 | * indicating that the item with the given ID was selected.
174 | */
175 | @Override
176 | public void onItemView(Account account) {
177 | if (mTwoPane) {
178 | // In two-pane mode, show the detail view in this activity by
179 | // adding or replacing the detail fragment using a
180 | // fragment transaction.
181 | Bundle arguments = new Bundle();
182 | arguments.putString(AccountDetailFragment.ARG_ITEM_ID, account.getId());
183 | AccountDetailFragment fragment = new AccountDetailFragment();
184 | fragment.setArguments(arguments);
185 | getFragmentManager().beginTransaction()
186 | .replace(R.id.account_detail_container, fragment)
187 | .commit();
188 |
189 | } else {
190 | // In single-pane mode, simply start the detail activity
191 | // for the selected item ID.
192 | Intent detailIntent = new Intent(this, AccountDetailActivity.class);
193 | detailIntent.putExtra(AccountDetailFragment.ARG_ITEM_ID, account.getId());
194 | getAccountListFragment().saveAccountListState(detailIntent);
195 | startActivity(detailIntent);
196 | }
197 | }
198 |
199 | @Override
200 | public void onFolderSelected(Account account) {
201 | // do anything that we need to do here
202 | }
203 |
204 | @Override
205 | public void onItemManuallySelected(Account account) {
206 | PwmApplication.getInstance().getAccountManager().selectAccountById(account.getId());
207 | NavUtils.navigateUpFromSameTask(this);
208 | Toast.makeText(this, "Manually selected '" + account.getName() + "'", Toast.LENGTH_SHORT).show();
209 | }
210 |
211 | private void setDisplayHomeAsUpEnabled() {
212 | ActionBar actionBar = getSupportActionBar();
213 | if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true);
214 | }
215 |
216 | @Override
217 | protected void onSaveInstanceState(@NotNull Bundle outState) {
218 | Log.i(LOG_TAG, "onSaveInstanceState");
219 | super.onSaveInstanceState(outState);
220 | }
221 |
222 | @Override
223 | protected void onRestoreInstanceState(@NotNull Bundle savedInstanceState) {
224 | Log.i(LOG_TAG, "onRestoreInstanceState");
225 | super.onRestoreInstanceState(savedInstanceState);
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/ClassicSettingsImporter.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 | import com.google.common.collect.Lists;
6 | import com.google.common.collect.Sets;
7 | import com.tasermonkeys.google.json.*;
8 | import com.tasermonkeys.google.json.reflect.TypeToken;
9 | import org.daveware.passwordmaker.*;
10 | import org.jetbrains.annotations.NotNull;
11 | import org.jetbrains.annotations.Nullable;
12 |
13 | import java.io.*;
14 | import java.util.*;
15 |
16 | import static com.google.common.io.Closeables.closeQuietly;
17 |
18 | public class ClassicSettingsImporter {
19 | private final Database database = new Database();
20 | private final Set favorites = Sets.newHashSet();
21 | private final Gson gson = new Gson();
22 | private static final String REPO_PROFILES_FILENAME = "profiles.pss";
23 | private static final String UPGRADED_MARKER = "profiles.upgrademarker";
24 | private static final String LOG_TAG = Logtags.CLASSIC_SETTINGS_IMPORTER.getTag();
25 |
26 |
27 | private ClassicSettingsImporter(Reader reader) throws IOException {
28 | readFromReader(reader);
29 | }
30 |
31 | private void readFromReader(Reader reader) throws IOException {
32 | JsonParser parser = new JsonParser();
33 | JsonElement element = parser.parse(reader);
34 | JsonObject obj = element.getAsJsonObject();
35 | parseAccountList(database.getRootAccount(), obj);
36 | }
37 |
38 | protected Database toDatabase() {
39 | return database;
40 | }
41 |
42 | protected Collection getFavorites() {
43 | return favorites;
44 | }
45 |
46 | /**
47 | * This function will only upgrade once. After the first time ran, its a no-op.
48 | * Side effect is a new file on the filesystem will be created named {@value #UPGRADED_MARKER}
49 | * @param context - The context to which base the filesystem calls on
50 | * @param favoritesOut - it clears it out, then add all favorites from all of the extracted profiles
51 | *
52 | * @return a created database if successful. Null otherwise.
53 | * @throws IOException - On error importing the database.
54 | */
55 | @Nullable
56 | public static Database importIntoDatabase(@NotNull Context context, @NotNull Collection favoritesOut) throws IOException {
57 | File f = new File(context.getFilesDir(), REPO_PROFILES_FILENAME);
58 | File upgradedMarker = new File(context.getFilesDir(), UPGRADED_MARKER);
59 | // The upgrade marker is so that we can only upgrade once, but at the same time allow for the possibility of a
60 | // downgrade of software version if a bug happens.
61 | if (!f.exists() || upgradedMarker.exists() )
62 | return null;
63 | Log.i(LOG_TAG, "Upgrading classic database to new one");
64 | InputStream fis = context.openFileInput(REPO_PROFILES_FILENAME);
65 | Database db = null;
66 | try {
67 | Reader reader = new InputStreamReader(fis, "UTF-8");
68 | ClassicSettingsImporter importer = new ClassicSettingsImporter(reader);
69 | db = importer.toDatabase();
70 | favoritesOut.clear();
71 | favoritesOut.addAll(importer.getFavorites());
72 | } finally {
73 | closeQuietly(fis);
74 | }
75 | // only will get here if we didn't throw before
76 | FileOutputStream touchFile = null;
77 | try {
78 | touchFile = new FileOutputStream(upgradedMarker);
79 | touchFile.write(new Date().toString().getBytes());
80 | } catch (IOException ignored) {
81 | } finally {
82 | try {
83 | if ( touchFile != null ) touchFile.close();
84 | } catch (IOException ignore) {}
85 | }
86 | return db;
87 | }
88 |
89 | protected void parseAccountList(Account parent, JsonObject accounts) throws IOException {
90 | for (Map.Entry x : accounts.entrySet()) {
91 | try {
92 | database.addAccount(parent, parseAccount((JsonObject)x.getValue()) );
93 | } catch (Exception e) {
94 | throw new IOException(e);
95 | }
96 | }
97 | }
98 |
99 | private Account parseAccount(JsonObject jsonAccount) {
100 | Account account = new Account(jsonAccount.get("name").getAsString(), "", jsonAccount.get("username").getAsString());
101 | account.setId(Account.createId());
102 | account.setCharacterSet(jsonAccount.get("characters").getAsString());
103 | OldHashAlgo hashAlgo = OldHashAlgo.valueOf(jsonAccount.get("currentAlgo").getAsString());
104 | account.setAlgorithm(hashAlgo.algo.getAlgo());
105 | account.setHmac(hashAlgo.algo.isHMac());
106 | account.setTrim(hashAlgo.algo.isTrimmed());
107 | OldLeetLevel leetLevel =OldLeetLevel.valueOf(jsonAccount.get("leetLevel").getAsString());
108 | account.setLeetLevel(leetLevel.leetLevel);
109 | OldUseLeet useLeet = OldUseLeet.valueOf(jsonAccount.get("useLeet").getAsString());
110 | account.setLeetType(useLeet.leetType);
111 | account.setModifier(jsonAccount.get("modifier").getAsString());
112 | account.setPrefix(jsonAccount.get("passwordPrefix").getAsString());
113 | account.setSuffix(jsonAccount.get("passwordSuffix").getAsString());
114 | Set esUrls = parseUrlParts(jsonAccount.get("urlComponents"));
115 | account.setUrlComponents(esUrls);
116 | account.setLength(jsonAccount.get("lengthOfPassword").getAsShort());
117 | List accountFavs = asStringArray(jsonAccount.getAsJsonArray("pwmFavoriteInputs"));
118 | // this will allow these accounts to be auto-selected for the favorites
119 | for (String fav : accountFavs ) {
120 | AccountPatternData accPtnData = new AccountPatternData();
121 | accPtnData.setDesc(fav + ": Imported from classic");
122 | accPtnData.setEnabled(true);
123 | accPtnData.setPattern(fav);
124 | accPtnData.setType(AccountPatternType.WILDCARD);
125 | account.getPatterns().add(accPtnData);
126 | }
127 | favorites.addAll(accountFavs);
128 |
129 | return account;
130 | }
131 |
132 | private List asStringArray(JsonArray array) {
133 | List errs = Lists.newArrayList();
134 | List result = Lists.newArrayListWithCapacity(array.size());
135 | for (int i = 0; i < array.size(); ++i) {
136 | try {
137 | result.add(array.get(i).getAsString());
138 | } catch (Exception e) {
139 | errs.add(e.getMessage());
140 | }
141 | }
142 | if ( ! errs.isEmpty() ) {
143 | Log.e(LOG_TAG, "Error while reading json array: " + errs.toString(), new IOException(errs.get(0)));
144 | }
145 | return result;
146 | }
147 |
148 | private Set parseUrlParts(JsonElement urlSetElement) {
149 | List urlComponents = gson.fromJson( urlSetElement, new TypeToken>() {}.getType());
150 | Set esUrls = EnumSet.noneOf(Account.UrlComponents.class);
151 | for (String urlComp : urlComponents) {
152 | esUrls.add(OldUrlComponents.valueOf(urlComp).urlComponent);
153 | }
154 | return esUrls;
155 | }
156 |
157 | private enum OldHashAlgo {
158 | MD4(AlgorithmSelectionValues.MD4),
159 | HMAC_MD4(AlgorithmSelectionValues.HMAC_MD4),
160 | MD5(AlgorithmSelectionValues.MD5),
161 | MD5_Version_0_6(AlgorithmSelectionValues.MD5_06),
162 | HMAC_MD5(AlgorithmSelectionValues.HMAC_MD5),
163 | HMAC_MD5_Version_0_6(AlgorithmSelectionValues.HMAC_MD5_06),
164 | SHA_1(AlgorithmSelectionValues.SHA1),
165 | HMAC_SHA_1(AlgorithmSelectionValues.HMAC_SHA1),
166 | SHA_256(AlgorithmSelectionValues.HMAC_SHA256),
167 | HMAC_SHA_256(AlgorithmSelectionValues.HMAC_SHA256),
168 | HMAC_SHA_256_Version_1_5_1(AlgorithmSelectionValues.HMAC_SHA256),
169 | RIPEMD_160(AlgorithmSelectionValues.RIPEMD160),
170 | HMAC_RIPEMD_160(AlgorithmSelectionValues.HMAC_RIPEMD160);
171 |
172 | final AlgorithmSelectionValues algo;
173 |
174 | OldHashAlgo(AlgorithmSelectionValues algo) {
175 | this.algo = algo;
176 | }
177 | }
178 |
179 | private enum OldLeetLevel {
180 | One(LeetLevel.LEVEL1),
181 | Two(LeetLevel.LEVEL2),
182 | Three(LeetLevel.LEVEL3),
183 | Four(LeetLevel.LEVEL4),
184 | Five(LeetLevel.LEVEL5),
185 | Six(LeetLevel.LEVEL6),
186 | Seven(LeetLevel.LEVEL7),
187 | Eight(LeetLevel.LEVEL8),
188 | Nine(LeetLevel.LEVEL9);
189 |
190 | public final LeetLevel leetLevel;
191 |
192 | OldLeetLevel(LeetLevel leetLevel) {
193 | this.leetLevel = leetLevel;
194 | }
195 | }
196 |
197 | private enum OldUseLeet {
198 | NotAtAll(LeetType.NONE),
199 | BeforeGeneratingPassword(LeetType.BEFORE),
200 | AfterGeneratingPassword(LeetType.AFTER),
201 | BeforeAndAfterGeneratingPassword(LeetType.BOTH);
202 |
203 | public final LeetType leetType;
204 |
205 | OldUseLeet(LeetType leetType) {
206 | this.leetType = leetType;
207 | }
208 | }
209 |
210 | private enum OldUrlComponents {
211 | Protocol(Account.UrlComponents.Protocol),
212 | Subdomain(Account.UrlComponents.Subdomain),
213 | Domain(Account.UrlComponents.Domain),
214 | PortPathAnchorQuery(Account.UrlComponents.PortPathAnchorQuery);
215 |
216 | public final Account.UrlComponents urlComponent;
217 |
218 |
219 | OldUrlComponents(Account.UrlComponents urlComponent) {
220 | this.urlComponent = urlComponent;
221 | }
222 |
223 | }
224 |
225 |
226 | }
227 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Passwordmaker Pro for Android
2 | ===========
3 |
4 | This is the android implementation of the Passwordmaker Pro algorithm designed by [Passwordmaker.org](http://passwordmaker.org). View the [Android public webpage](http://android.passwordmaker.org).
5 |
6 | How it works:
7 | You provide PasswordMaker two pieces of information: a "master password" -- that one, single password you like -- and
8 | the URL of the website requiring a password. Through the magic of one-way hash algorithms, PasswordMaker calculates a
9 | message digest(hash), also known as a digital fingerprint, which can be used as your password for the website.
10 | Although one-way hash algorithms have a number of interesting characteristics, the one capitalized by PasswordMaker
11 | is that the resulting fingerprint (password) does "not reveal anything about the input that was used to generate it."
12 | In other words, if someone has one or more of your generated passwords, it is computationally infeasible for him
13 | to derive your master password or to calculate your other passwords. Computationally infeasible means even computers
14 | like this won't help!
15 |
16 | There are the same tools that you can download for many of the popular browsers, and other mobile devices. See the
17 | [Passwordmaker.org](http://passwordmaker.org) website for more information.
18 |
19 | For bug reports please see the [issue tracker](https://github.com/passwordmaker/android-passwordmaker/issues).
20 |
21 | Feel free to create a pull request to fix a bug or add a feature yourself!
22 |
23 | Users
24 | ======
25 | Welcome to passwordmaker! If you haven't already checkout [Passwordmaker.org](http://passwordmaker.org) to know what password maker is all about.
26 |
27 | If you have an issues or questions, with the Android version of PasswordMaker Pro, go to the [Issue Tracker](https://github.com/passwordmaker/android-passwordmaker/issues)
28 |
29 | Have a comment, question, or issue that you want to say more privately not on the issue tracker, feel free to [email](mailto:pwdmkrpro.android.84a75@tasermonkeys.com) me.
30 |
31 | [Developer's keybase.io account](https://keybase.io/jstapleton)
32 |
33 | Developers
34 | ==========
35 | If you are not a developer you probably don't need to read any farther down this file.
36 |
37 | Repository layout
38 | ==================
39 | This project uses the [Git Flow](http://nvie.com/posts/a-successful-git-branching-model/) layout. You can download a helper plugin for git from https://github.com/nvie/gitflow repository.
40 |
41 | Compiling
42 | ==========
43 | * You will ofcourse need to download the [Android SDK](http://developer.android.com/sdk/index.html#download)
44 | * Download and install an IDE like [IntelliJ](http://www.jetbrains.com/idea/) with its plugin for Android.
45 | * Until [passwordmaker-je-lib](https://github.com/passwordmaker/java-passwordmaker-lib) makes it into maven central, check it out and do a mvn install on it first.
46 |
47 | I now set this up using the gradle build process. I use the Intellij IDE which makes the process really easy. Just
48 | install the Intellij Android plugin and import this project after cloning this repository. Use the import from external
49 | model, then choose "gradle". It should take care of the rest. Intellij created gradle wrappers which I believe you can
50 | just run `./gradlew `
51 |
52 | See `./gradlew tasks` for all of the tasks you wish to do.
53 |
54 | For example to run the Android Test cases, run: `./gradlew connectedAndroidTest`
55 |
56 | Though I heavily suggest using an IDE like Intellij or Eclipse.
57 |
58 | Step by Step Compiling in the commandline
59 | ===========
60 | ### Step 1: get build, and install passwordmaker-je
61 |
62 | git clone https://github.com/tasermonkey/passwordmaker-je-lib.git
63 | cd passwordmaker-je-lib
64 | git checkout 0.9.3
65 | mvn install
66 |
67 | ### Step 2: get and build android passwordmaker
68 |
69 | # the cd .. is just to go to the same parent directory as the passwordmaker-je-lib to checkout the android code
70 | cd ..
71 | git clone https://github.com/tasermonkey/android-passwordmaker.git
72 | git checkout release/v2.0.0
73 | cd android-passwordmaker/passwordmaker
74 | ../gradlew assembleDebug
75 |
76 | ### Step 3: apk
77 | This should have built an apk inside of the android-passwordmaker/passwordmaker/build/apk directory: passwordmaker-debug.apk
78 |
79 | To use gradle to install this, run:
80 |
81 | ../gradlew installDebug
82 |
83 | To assemble the release mode, you run the task `assembleRelease` and to install: `installRelease`. However in order to build this you need to setup the signing.
84 |
85 | Signing
86 | ==========
87 | In order to build this project you need to setup signing. I can't just include the signing keys in the git repo because that would mean anyone could sign as me.
88 | So you will need to generate your own signing keys. See [Android signing help](http://developer.android.com/tools/publishing/app-signing.html)
89 |
90 | Then from the `android-passwordmaker/passwordmaker` you need to setup your Environment by running the script:
91 |
92 | source set_signing_env_vars.sh
93 |
94 | This script is required to be 'sourced' from your shell whenever you open up a new shell, and be source because it adds 4 environment variables to your session.
95 | NOTE: This seems like a pretty decent way to do this, as the password will never be visible. Also never stored on disk. However, maybe somehow invent a gpg way of storing the password and config info.
96 |
97 |
98 | Notes
99 | ======
100 | * Why does PasswordMakerPro require using `spongycastle`?
101 | - Because Android provides part of `BouncyCastle`, but not all of it. And some of the Hash algorithms used by passwordmaker didn't make the cut.
102 | - Spongycastle is basically just `BouncyCastle` moved to a different package. Then renamed to `SC` as its provider name.
103 | * Why did you need to Shim out the XmlWriter interface?
104 | - Because once against, Android moved out some of the `javax.xml.streaming.*` classes and it doesn't work.
105 | - So the solution was to just shim out the interface, then have it use the xmlpull package that comes with Android.
106 | * Why did you want to move to use the java library `passwordmaker-je`?
107 | - This way any improvements to the core can be made to both the standard `java edition` and the android edition!
108 | - See the Java library code repository here: [passwordmaker-je-lib](http://github.com/tasermonkey/passwordmaker-je-lib)
109 | - The original source came from: http://code.google.com/p/passwordmaker-je/
110 | - Thanks to Dave Marotti for this
111 | * Version Code strategy:
112 | - `..` -> code: First digit is major, second two digits is minor, and last two digits are revision.
113 | - Example: for `2.13.4` the code would be `21304` This way as the version number increases the code will also be a bigger number
114 | - This is different for the first 10 released versions which were just the release numbers.
115 | * What the blank, mvn and gradle?
116 | - Yeah, until I fixed up the build for the passwordmaker-je-lib more, it is using maven.
117 | - And this project is using gradle, cause really, I like gradle, other than its slow to startup. But it works well with android dev.
118 |
119 | Deploying using the android developer webpage and Android Studio
120 | =======================
121 | 1. In Android Studio go to action: `Generate Signed Bundled`
122 | 1. Choose `Android App Bundle`
123 | 1. Locate keystore, select alias `passwordmakerproforandroid` and type in the correct password.
124 | 1. Select destination folder and use build type `releases`
125 | 1. Locate the release bundle (Android Studio should prompt for it): something named `passwordmaker.aab`
126 | 1. Log into Android developer Play console: https://play.google.com/apps/publish
127 | 1. Within the application for passwordmaker go to the `Release management` / `App Releases`
128 | 1. Select `Manage` from the `Production` track
129 | 1. Click on the button `Create Release`
130 | 1. Give it the signed bundle generated.
131 | 1. Ensure the internal version is reasonable.
132 | 1. give a short description on what changed
133 | 1. Click Save, followed by 'Review'
134 | 1. Let it get deployed.
135 |
136 | Adding the built artifacts to releases in Github
137 | =======================
138 | - As a place to keep all of the versions of the software, its nice to use the releases section of github.
139 | - Should now release both the aab and the apk.
140 | - In Android studio go to build/'Generate signed bundled/apk'
141 | - We will do both options, one at a time (Android App Bundle and then APK)
142 | - If you followed the instructions to upload to the Play Store, you already did the signed app bundled.
143 | - So just go throught the Build signed 'APK' option
144 | - Locate keystore, select alias `passwordmakerproforandroid` and type in the correct password.
145 | - Select destination folder and use build type `releases`
146 | - Ensure both v1 and v2 `Signature Versions` are selected.
147 | - Click finished.
148 | - Find the files in the 'releases' directory and upload it as the release for the new tagged version.
149 |
150 |
151 | Status of this project
152 | =======================
153 |
154 | ## Update 2014-07-17 by @tasermonkey
155 |
156 | Release [Version 2.0.1](https://github.com/passwordmaker/android-passwordmaker/releases/tag/v2.0.1)
157 |
158 | # Note on Patches/Pull Requests:
159 |
160 | * Fork the project.
161 | * Make your feature addition or bug fix.
162 | * Commit, do not mess with version in build.gradle
163 | (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
164 | * Send me a pull request. Bonus points for feature branches.
165 |
166 | Copyright
167 | ==========
168 |
169 | Copyright (c) 2010 [James Stapleton](https://keybase.io/jstapleton) and [PasswordMaker.org](http://passwordmaker.org) . See LICENSE for details. A list of all contributors can be found at the [contributors](http://github.com/passwordmaker/android-passwordmaker/contributors) page.
170 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/PwmApplication.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.content.Context;
4 | import android.util.Log;
5 | import com.google.common.collect.Lists;
6 | import org.daveware.passwordmaker.*;
7 | import org.passwordmaker.AccountManagerSamples;
8 |
9 | import java.io.*;
10 | import java.security.Security;
11 | import java.util.*;
12 |
13 | import static com.google.common.base.Strings.nullToEmpty;
14 |
15 | /**
16 | * The page http://developer.android.com/reference/android/app/Application.html
17 | * suggest that we shouldn't extend from Application unless we need too, so lets give this a whirl and see how far I can
18 | * get without needing to extend from Application. Right now, I don't need to know anything else about the application
19 | * lifestyle.
20 | *
21 | * This should only be used from the application's UI thread. As both this class, and AccountManager isn't thread safe.
22 | * E.g. any execution point in the application that came from an on::Event:: (e.g. onCreate, onButtonClick, etc). Should
23 | * be very careful not to use this from another thread. Use a method to get an event on the UI thread if need to read/modify
24 | * this data.
25 | *
26 | * See: Activity.runOnUiThread(Runnable)
27 | * View.post(Runnable)
28 | * View.postDelayed(Runnable, long)
29 | * on how to get events to the UI thread from a non-UI thread. Really you should read:
30 | * http://developer.android.com/guide/components/processes-and-threads.html on better examples, for example use of the
31 | * AsyncTask ( http://developer.android.com/reference/android/os/AsyncTask.html ) is probably better use of a background
32 | * task, with something that needs to update something on the UI thread.
33 | *
34 | *
35 | * The reason why this class is lazily loaded, is to ensure that we are created after the android system is setup. Eg.
36 | * the first use of this should be from the Main Activity's onCreate() (or later).
37 | *
38 | * XXX - This class needs some refactoring already.
39 | *
40 | */
41 | public class PwmApplication {
42 |
43 | private static final String LOG_TAG = Logtags.PWM_APPLICATION.getTag();
44 | private static final String PROFILE_DB_FILE = "profile_database.rdf";
45 |
46 | private static PwmApplication sInstance;
47 | private boolean firstTimeLoading = true;
48 | private final AccountManager accountManager;
49 |
50 |
51 | public static PwmApplication getInstance() {
52 | // Lazy load the singleton on first use.
53 | if ( sInstance == null ) {
54 | sInstance = new PwmApplication();
55 | }
56 | return sInstance;
57 | }
58 |
59 | private PwmApplication() {
60 | PasswordMaker.setDefaultCryptoProvider("SC");
61 | Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
62 | accountManager = new AccountManager();
63 | AccountManagerSamples.addSamples(accountManager);
64 | }
65 |
66 | public AccountManager getAccountManager() {
67 | return accountManager;
68 | }
69 |
70 | public void saveSettings(Context context) {
71 | String toWrite = serializeSettings();
72 | FileOutputStream fos = null;
73 | try {
74 | fos = context.openFileOutput(PROFILE_DB_FILE, Context.MODE_PRIVATE);
75 | fos.write(toWrite.getBytes());
76 | Log.i(LOG_TAG, "Saved application settings");
77 | } catch (FileNotFoundException e) {
78 | throw new RuntimeException(e);
79 | } catch (IOException e) {
80 | throw new RuntimeException(e);
81 | } finally {
82 | if (fos != null) {
83 | try {
84 | fos.close();
85 | } catch (IOException e) {
86 | Log.e(LOG_TAG, "Unable to close profile_database.rdf after writing", e);
87 | }
88 | }
89 | }
90 | }
91 |
92 | protected void loadSettings(Context context) {
93 |
94 | LoadResults results = loadClassic(context);
95 | if ( results == null ) results = loadFromRDF(context);
96 | // this only happens if we successfully loaded up the db
97 | if ( results != null ) {
98 | Log.i(LOG_TAG, "Loaded application settings");
99 | accountManager.getPwmProfiles().swapAccounts(results.database);
100 | loadFavoritesFromGlobalSettings();
101 | loadMasterPasswordHashFromGlobalSettings();
102 | if ( results.favorites != null ) accountManager.addFavoriteUrls(results.favorites);
103 | }
104 | }
105 |
106 | protected String serializeSettings() {
107 | AndroidRDFDatabaseWriter writer = new AndroidRDFDatabaseWriter();
108 | ByteArrayOutputStream os = new ByteArrayOutputStream();
109 | try {
110 | updateFavoritesGlobalSettings();
111 | updateMasterPasswordHash();
112 | writer.write(os, accountManager.getPwmProfiles());
113 | return os.toString();
114 | } catch (Exception e) {
115 | throw new RuntimeException(e);
116 | }
117 | }
118 |
119 | public String serializeSettingsWithOutMasterPassword() {
120 | SecureCharArray hash = cloneArray(accountManager.getCurrentPasswordHash());
121 | String salt = nullToEmpty(accountManager.getPasswordSalt());
122 | try {
123 | accountManager.disablePasswordHash();
124 | return serializeSettings();
125 | } finally {
126 | try {
127 | accountManager.replaceCurrentPasswordHash(hash, salt);
128 | } catch (Exception ignored) {
129 | }
130 | }
131 | }
132 |
133 | protected SecureCharArray cloneArray(SecureCharArray chars) {
134 | if ( chars != null )
135 | return new SecureCharArray(chars);
136 | else
137 | return new SecureCharArray();
138 | }
139 |
140 | protected Database deserializedSettings(InputStream is, boolean convertBuggyAlgo, List errors) {
141 | RDFDatabaseReader reader = new RDFDatabaseReader();
142 | if ( convertBuggyAlgo ) reader.setBuggyAlgoUseAction(DatabaseReader.BuggyAlgoAction.CONVERT);
143 | try {
144 | return reader.read(is);
145 | } catch (Exception e) {
146 | throw new RuntimeException(e);
147 | } finally {
148 | errors.addAll(reader.getIncompatibleAccounts());
149 | }
150 | }
151 |
152 | protected Database deserializedSettings(InputStream is, boolean convertBuggyAlgo) {
153 | return deserializedSettings(is, convertBuggyAlgo, new ArrayList());
154 | }
155 |
156 | public Database deserializedSettings(String serialized, boolean convertBuggyAlgo, List errors) {
157 | ByteArrayInputStream is = new ByteArrayInputStream(serialized.getBytes());
158 | return deserializedSettings(is, convertBuggyAlgo, errors);
159 | }
160 |
161 | @SuppressWarnings("UnusedDeclaration")
162 | public Database deserializedSettings(String serialized, boolean convertBuggyAlgo) {
163 | ByteArrayInputStream is = new ByteArrayInputStream(serialized.getBytes());
164 | return deserializedSettings(is, convertBuggyAlgo);
165 | }
166 |
167 | protected void updateFavoritesGlobalSettings() {
168 | String encodedUrls = accountManager.encodeFavoriteUrls();
169 | accountManager.getPwmProfiles().setGlobalSetting(AndroidGlobalSettings.FAVORITES, encodedUrls);
170 | }
171 |
172 | public void loadFavoritesFromGlobalSettings() {
173 | String encodedUrls = accountManager.getPwmProfiles().getGlobalSetting(AndroidGlobalSettings.FAVORITES);
174 | accountManager.decodeFavoritesUrls(encodedUrls, true);
175 | }
176 |
177 | protected void updateMasterPasswordHash() {
178 | SecureCharArray pwdSCA = accountManager.getCurrentPasswordHash();
179 | String pwdHash = pwdSCA != null ? new String(accountManager.getCurrentPasswordHash().getData()) : "";
180 | String pwdSalt = accountManager.getPasswordSalt();
181 | boolean pwdStore = accountManager.shouldStorePasswordHash();
182 | if ( pwdStore && pwdHash.length() > 0 && pwdSalt.length() > 0) {
183 | accountManager.getPwmProfiles().setGlobalSetting(AndroidGlobalSettings.MASTER_PASSWORD_HASH, pwdHash);
184 | accountManager.getPwmProfiles().setGlobalSetting(AndroidGlobalSettings.MASTER_PASSWORD_SALT, pwdSalt);
185 | } else {
186 | // ensure its cleared out
187 | accountManager.getPwmProfiles().setGlobalSetting(AndroidGlobalSettings.MASTER_PASSWORD_HASH, "");
188 | accountManager.getPwmProfiles().setGlobalSetting(AndroidGlobalSettings.MASTER_PASSWORD_SALT, "");
189 | }
190 | accountManager.getPwmProfiles().setGlobalSetting(AndroidGlobalSettings.STORE_MASTER_PASSWORD_HASH,
191 | Boolean.toString(pwdStore));
192 | }
193 |
194 | protected void loadMasterPasswordHashFromGlobalSettings() {
195 | boolean pwdStore = Boolean.parseBoolean(
196 | accountManager.getPwmProfiles().getGlobalSetting(AndroidGlobalSettings.STORE_MASTER_PASSWORD_HASH));
197 | if ( !pwdStore ) {
198 | accountManager.disablePasswordHash();
199 | } else {
200 | String pwdHash = accountManager.getPwmProfiles().getGlobalSetting(AndroidGlobalSettings.MASTER_PASSWORD_HASH);
201 | String pwdSalt = accountManager.getPwmProfiles().getGlobalSetting(AndroidGlobalSettings.MASTER_PASSWORD_SALT);
202 | accountManager.replaceCurrentPasswordHash(new SecureCharArray(pwdHash), pwdSalt);
203 | }
204 | }
205 |
206 | public void loadSettingsOnce(Context context) {
207 | if ( firstTimeLoading ) {
208 | loadSettings(context);
209 | firstTimeLoading = false;
210 | }
211 | }
212 |
213 |
214 | public Set getAllAccountsUrls() {
215 | Set result = new HashSet();
216 | getAllSubAccountsUrls(accountManager.getPwmProfiles().getRootAccount(), result);
217 | return result;
218 | }
219 |
220 | private static void getAllSubAccountsUrls(Account account, Set urls) {
221 | if ( isNotEmpty(account.getUrl()) ) {
222 | urls.add(account.getUrl());
223 | }
224 | if ( account.hasChildren() ) {
225 | for (Account child : account.getChildren() ) {
226 | getAllSubAccountsUrls(child, urls);
227 | }
228 | }
229 | }
230 |
231 | private static boolean isNotEmpty(String s) {
232 | return s != null && !s.isEmpty();
233 | }
234 |
235 | // This class shouldn't be required, this has bad code smell
236 | private static class LoadResults {
237 | private final Database database;
238 | private final Collection favorites;
239 |
240 | private LoadResults(Database database, Collection favorites) {
241 | this.database = database;
242 | this.favorites = favorites;
243 | }
244 | }
245 |
246 | private LoadResults loadClassic(Context context) {
247 | try {
248 | Collection favorites = Lists.newArrayList();
249 | Database db = ClassicSettingsImporter.importIntoDatabase(context, favorites);
250 | if ( db != null)
251 | return new LoadResults(db, favorites);
252 | } catch (IOException e) {
253 | Log.e(LOG_TAG, "Error importing classic version database", e);
254 | }
255 | return null;
256 | }
257 |
258 | private LoadResults loadFromRDF(Context context) {
259 | File f = new File(context.getFilesDir(), PROFILE_DB_FILE);
260 | if ( ! f.exists() )
261 | return null;
262 | if ( ! f.canRead() ) {
263 | Log.e(LOG_TAG, "Can not read settings file: " + f.getAbsolutePath());
264 | return null;
265 | }
266 | InputStream fis = null;
267 | try {
268 | fis = context.openFileInput(PROFILE_DB_FILE);
269 | return new LoadResults(deserializedSettings(fis, false), null);
270 | } catch (FileNotFoundException e) {
271 | Log.e(LOG_TAG, "Unable to read profile", e);
272 | } finally {
273 | if (fis != null) {
274 | try {
275 | fis.close();
276 | } catch (IOException e) {
277 | Log.e(LOG_TAG, "Unable to close profile_database.rdf after reading", e);
278 | }
279 | }
280 | }
281 | return null;
282 | }
283 |
284 | }
285 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/AccountListFragment.java:
--------------------------------------------------------------------------------
1 | package org.passwordmaker.android;
2 |
3 | import android.app.Activity;
4 | import android.app.ListFragment;
5 | import android.content.Intent;
6 | import android.os.Bundle;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.support.v7.view.ActionMode;
9 | import android.view.Menu;
10 | import android.view.MenuInflater;
11 | import android.view.MenuItem;
12 | import android.view.View;
13 | import android.widget.ArrayAdapter;
14 | import android.widget.ListView;
15 | import com.google.common.base.Function;
16 | import com.google.common.base.Predicate;
17 | import com.google.common.collect.Collections2;
18 | import com.google.common.collect.Lists;
19 | import org.daveware.passwordmaker.Account;
20 | import org.daveware.passwordmaker.AccountManager;
21 | import org.jetbrains.annotations.NotNull;
22 | import org.jetbrains.annotations.Nullable;
23 |
24 | import java.util.ArrayList;
25 | import java.util.Collection;
26 | import java.util.LinkedList;
27 | import java.util.List;
28 |
29 | /**
30 | * A list fragment representing a list of Accounts. This fragment
31 | * also supports tablet devices by allowing list items to be given an
32 | * 'activated' state upon selection. This helps indicate which item is
33 | * currently being viewed in a {@link AccountDetailFragment}.
34 | *
35 | * Activities containing this fragment MUST implement the {@link Callbacks}
36 | * interface.
37 | */
38 | public class AccountListFragment extends ListFragment {
39 |
40 | /**
41 | * The serialization (saved instance state) Bundle key representing the
42 | * activated item position. Only used on tablets.
43 | */
44 | private static final String STATE_ACTIVATED_POSITION = "activated_position";
45 | public static final String STATE_ACCOUNT_STACK = "activated_account_stack";
46 | @SuppressWarnings("UnusedDeclaration")
47 | private static final String LOG_TAG = Logtags.ACCOUNT_LIST_FRAGMENT.getTag();
48 | /**
49 | * The fragment's current callback object, which is notified of list item
50 | * clicks.
51 | */
52 | private Callbacks mCallbacks = sDummyCallbacks;
53 |
54 | private ActionMode mActionMode;
55 | private Menu mActionMenu;
56 |
57 | private boolean autoActivateMode = false;
58 |
59 | /**
60 | * The current activated item position. Only used on tablets.
61 | */
62 | private int mActivatedPosition = ListView.INVALID_POSITION;
63 | private AccountManager accountManager;
64 | private Account loadedAccount;
65 |
66 | /**
67 | * A callback interface that all activities containing this fragment must
68 | * implement. This mechanism allows activities to be notified of item
69 | * selections.
70 | */
71 | @SuppressWarnings("EmptyMethod")
72 | public interface Callbacks {
73 | /**
74 | * Callback for when an item has been selected.
75 | */
76 | public void onItemView(Account account);
77 | public void onFolderSelected(Account account);
78 | public void onItemManuallySelected(Account account);
79 | }
80 |
81 | /**
82 | * A dummy implementation of the {@link Callbacks} interface that does
83 | * nothing. Used only when this fragment is not attached to an activity.
84 | */
85 | private final static Callbacks sDummyCallbacks = new Callbacks() {
86 | @Override
87 | public void onItemView(Account account) {
88 | }
89 |
90 | @Override
91 | public void onFolderSelected(Account account) {
92 |
93 | }
94 |
95 | @Override
96 | public void onItemManuallySelected(Account account) {
97 |
98 | }
99 | };
100 |
101 | private final AccountStack accountStack = new AccountStack();
102 |
103 |
104 |
105 | /**
106 | * Mandatory empty constructor for the fragment manager to instantiate the
107 | * fragment (e.g. upon screen orientation changes).
108 | */
109 | public AccountListFragment() {
110 | }
111 |
112 | @Override
113 | public void onCreate(Bundle savedInstanceState) {
114 | super.onCreate(savedInstanceState);
115 | accountManager = PwmApplication.getInstance().getAccountManager();
116 | setListAdapter(new ArrayAdapter(
117 | getActivity(),
118 | android.R.layout.simple_list_item_activated_1,
119 | android.R.id.text1));
120 | loadIncomingAccount();
121 | }
122 |
123 | @Override
124 | public void onViewCreated(View view, Bundle savedInstanceState) {
125 | super.onViewCreated(view, savedInstanceState);
126 | // Restore the previously serialized activated item position.
127 | if (savedInstanceState != null
128 | && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
129 | setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
130 | }
131 | }
132 |
133 | private void loadIncomingAccount() {
134 | Intent intent = getActivity().getIntent();
135 | String accountId = intent.getStringExtra(AccountDetailFragment.ARG_ITEM_ID);
136 | ArrayList accStack = intent.getStringArrayListExtra(AccountListFragment.STATE_ACCOUNT_STACK);
137 | if ( accStack == null || accStack.isEmpty()) {
138 | accountStack.clearToRoot();
139 | if ( accountId != null && !accountId.isEmpty() ) {
140 | List pathToAccount = accountManager.getPwmProfiles().findPathToAccountById(accountId);
141 | // this is saved so that we can notify the main list that we should display the details of the selected
142 | // account right when they set the callback
143 | loadedAccount = pathToAccount.remove(pathToAccount.size() - 1);
144 | accountStack.replace(pathToAccount);
145 |
146 | }
147 | } else {
148 | accountStack.loadFromIds(accStack);
149 | }
150 | refreshList(accountStack.getCurrentAccount());
151 | }
152 |
153 | @Override
154 | public void onAttach(Activity activity) {
155 | super.onAttach(activity);
156 |
157 | // Activities containing this fragment must implement its callbacks.
158 | if (!(activity instanceof Callbacks)) {
159 | throw new IllegalStateException("Activity must implement fragment's callbacks.");
160 | }
161 |
162 | mCallbacks = (Callbacks) activity;
163 | if ( loadedAccount != null ) {
164 | Account acc = loadedAccount;
165 | loadedAccount = null;
166 | mCallbacks.onItemView(acc);
167 | }
168 |
169 | }
170 |
171 | @Override
172 | public void onDetach() {
173 | super.onDetach();
174 | // Reset the active callbacks interface to the dummy implementation.
175 | mCallbacks = sDummyCallbacks;
176 | }
177 |
178 | @Override
179 | public void onListItemClick(ListView listView, View view, int position, long id) {
180 | super.onListItemClick(listView, view, position, id);
181 |
182 | // Notify the active callbacks interface (the activity, if the
183 | // fragment is attached to one) that an item has been selected.
184 | Account selected = getCurrentAccountList().getItem(position);
185 | if ( selected.hasChildren() ) {
186 | goIntoFolder(selected);
187 | } else {
188 | if (mActionMode != null ) {
189 | if (mActionMenu != null) {
190 | if (getCheckedAccount().isDefault()) {
191 | mActionMenu.findItem(R.id.menu_item_delete).setVisible(false);
192 | } else {
193 | mActionMenu.findItem(R.id.menu_item_delete).setVisible(true);
194 | }
195 | }
196 | return;
197 | }
198 | if ( ! autoActivateMode ) {
199 | getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
200 | }
201 | getListView().setItemChecked(position, true);
202 |
203 | // Start the CAB using the ActionMode.Callback defined above
204 | mActionMode = ((AppCompatActivity)getActivity()).startSupportActionMode(mActionModeCallback);
205 | }
206 | }
207 |
208 | protected void goIntoFolder(Account folder) {
209 | accountStack.pushCurrentAccount(folder);
210 | refreshList(folder);
211 | mCallbacks.onFolderSelected(folder);
212 | }
213 |
214 | @Override
215 | public void onSaveInstanceState(Bundle outState) {
216 | super.onSaveInstanceState(outState);
217 | if (mActivatedPosition != ListView.INVALID_POSITION) {
218 | // Serialize and persist the activated item position.
219 | outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
220 | }
221 | outState.putStringArrayList(STATE_ACCOUNT_STACK, accountStack.getIds());
222 | }
223 |
224 | public void saveAccountListState(Intent intent) {
225 | intent.putStringArrayListExtra(AccountListFragment.STATE_ACCOUNT_STACK, accountStack.getIds());
226 | }
227 |
228 | /**
229 | * Turns on activate-on-click mode. When this mode is on, list items will be
230 | * given the 'activated' state when touched.
231 | */
232 | public void setActivateOnItemClick() {
233 | // When setting CHOICE_MODE_SINGLE, ListView will automatically
234 | // give items the 'activated' state when touched.
235 | getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
236 | autoActivateMode = true;
237 | }
238 |
239 | private void setActivatedPosition(int position) {
240 | if (position == ListView.INVALID_POSITION) {
241 | getListView().setItemChecked(mActivatedPosition, false);
242 | } else {
243 | getListView().setItemChecked(position, true);
244 | }
245 |
246 | mActivatedPosition = position;
247 | }
248 |
249 | @SuppressWarnings("unchecked")
250 | private ArrayAdapter getCurrentAccountList() {
251 | return (ArrayAdapter) getListAdapter();
252 | }
253 |
254 | private void refreshList(@NotNull Account account) {
255 | ArrayAdapter accounts = getCurrentAccountList();
256 | accounts.setNotifyOnChange(false);
257 | accounts.clear();
258 | accounts.addAll(account.getChildren());
259 | accounts.notifyDataSetChanged();
260 | }
261 |
262 | public void createNewAccount(String accountName) {
263 | try {
264 | Account account = new Account(accountName, false);
265 | account.copySettings(accountManager.getDefaultAccount());
266 | account.setName(accountName);
267 | account.setIsFolder(false);
268 | account.clearUrlComponents();
269 | account.addUrlComponent(Account.UrlComponents.Domain);
270 | account.getPatterns().clear();
271 | accountManager.getPwmProfiles().addAccount(accountStack.getCurrentAccount(), account);
272 | getCurrentAccountList().notifyDataSetChanged();
273 | mCallbacks.onItemView(account);
274 | } catch (Exception e) {
275 | throw new RuntimeException(e);
276 | }
277 | }
278 |
279 | protected void deleteAccount(Account account) {
280 | accountManager.getPwmProfiles().removeAccount(account);
281 | refreshList(accountStack.getCurrentAccount());
282 | getCurrentAccountList().notifyDataSetChanged();
283 | getListView().clearChoices();
284 | }
285 |
286 |
287 | public void createNewFolder(String folderName) {
288 | try {
289 | Account account = new Account(folderName, true);
290 | accountManager.getPwmProfiles().addAccount(accountStack.getCurrentAccount(), account);
291 | getCurrentAccountList().notifyDataSetChanged();
292 | goIntoFolder(account);
293 | } catch (Exception e) {
294 | throw new RuntimeException(e);
295 | }
296 | }
297 |
298 | private void clearAllChecked() {
299 | final ListView lv = getListView();
300 | getListView().clearChoices();
301 | for (int i = 0; i < lv.getCount(); i++)
302 | lv.setItemChecked(i, false);
303 | if ( ! autoActivateMode ) {
304 | lv.post(new Runnable() {
305 | @Override
306 | public void run() {
307 | lv.setChoiceMode(ListView.CHOICE_MODE_NONE);
308 | }
309 | });
310 |
311 | }
312 | else if ( mActivatedPosition != ListView.INVALID_POSITION ) {
313 | setActivatedPosition(mActivatedPosition);
314 | }
315 | getCurrentAccountList().notifyDataSetChanged();
316 | }
317 |
318 | private class AccountStack {
319 | private final LinkedList accountStack = new LinkedList();
320 |
321 | public int size() {
322 | return accountStack.size();
323 | }
324 |
325 | public void clearToRoot() {
326 | pushCurrentAccount(null);
327 | }
328 |
329 | public void replace(Collection accounts) {
330 | clearToRoot();
331 | // can't use add all since it will be in reverse order
332 | for ( Account a : accounts ) accountStack.push(a);
333 | }
334 |
335 | // I think this might belong in the fragment
336 | public void pushCurrentAccount(@Nullable Account account) {
337 | if ( account == null ) {
338 | accountStack.clear();
339 | account = accountManager.getPwmProfiles().getRootAccount();
340 | }
341 | if ( accountStack.peek() != account )
342 | accountStack.push(account);
343 | }
344 |
345 | @SuppressWarnings("UnusedDeclaration")
346 | public Account popAccount() {
347 | // always ensure there is at least the root on the stack
348 | if ( accountStack.isEmpty() )
349 | accountStack.push(accountManager.getPwmProfiles().getRootAccount());
350 | return accountStack.peek();
351 | }
352 |
353 | @NotNull
354 | public Account getCurrentAccount() {
355 | if ( accountStack.isEmpty() ) {
356 | accountStack.push(accountManager.getPwmProfiles().getRootAccount());
357 | }
358 | return accountStack.peek();
359 | }
360 |
361 | public ArrayList getIds() {
362 | Collection accountStackIds = Lists.transform(accountStack, new Function() {
363 | @Override
364 | public String apply(Account account) {
365 | return account.getId();
366 | }
367 | });
368 | return new ArrayList(accountStackIds);
369 | }
370 |
371 | private void loadFromIds(List accountIdStack) {
372 | if ( accountIdStack == null ) return;
373 | accountStack.clear();
374 | Collection accounts = Collections2.filter(Lists.transform(accountIdStack,
375 | new Function() {
376 | @Override
377 | public Account apply(String id) {
378 | return accountManager.getPwmProfiles().findAccountById(id);
379 | }
380 | }), new Predicate() {
381 | @Override
382 | public boolean apply(Account account) {
383 | return account != null;
384 | }
385 | });
386 | accountStack.addAll(accounts);
387 | }
388 |
389 | }
390 |
391 |
392 | private final ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
393 |
394 | // Called when the action mode is created; startActionMode() was called
395 | @Override
396 | public boolean onCreateActionMode(ActionMode mode, Menu menu) {
397 | // Inflate a menu resource providing context menu items
398 | MenuInflater inflater = mode.getMenuInflater();
399 | inflater.inflate(R.menu.account_list_menu, menu);
400 | if (getCheckedAccount().isDefault()) {
401 | menu.findItem(R.id.menu_item_delete).setVisible(false);
402 | } else {
403 | menu.findItem(R.id.menu_item_delete).setVisible(true);
404 | }
405 | mActionMenu = menu;
406 | return true;
407 | }
408 |
409 | // Called each time the action mode is shown. Always called after onCreateActionMode, but
410 | // may be called multiple times if the mode is invalidated.
411 | @Override
412 | public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
413 | return false; // Return false if nothing is done
414 | }
415 |
416 | // Called when the user selects a contextual menu item
417 | @Override
418 | public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
419 | switch (item.getItemId()) {
420 | case R.id.menu_item_select:
421 | mCallbacks.onItemManuallySelected(getCheckedAccount());
422 | mode.finish(); // Action picked, so close the CAB
423 | return true;
424 | case R.id.menu_item_delete:
425 | deleteAccount(getCheckedAccount());
426 | mode.finish();
427 | return true;
428 | case R.id.menu_item_view:
429 | mCallbacks.onItemView(getCheckedAccount());
430 | mode.finish();
431 | return true;
432 | default:
433 | return false;
434 | }
435 | }
436 |
437 | // Called when the user exits the action mode
438 | @Override
439 | public void onDestroyActionMode(ActionMode mode) {
440 | mActionMode = null;
441 | mActionMenu = null;
442 | clearAllChecked();
443 | }
444 | };
445 |
446 |
447 |
448 | public Account getCheckedAccount() {
449 | return getCurrentAccountList().getItem(getListView().getCheckedItemPosition());
450 | }
451 | }
452 |
--------------------------------------------------------------------------------
/passwordmaker/src/main/java/org/passwordmaker/android/widgets/SwipeDismissListViewTouchListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Google Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | *
17 | * This file was originally from:
18 | * https://code.google.com/p/dashclock/source/browse/main/src/main/java/com/google/android/apps/dashclock/ui/SwipeDismissListViewTouchListener.java?spec=svnd043bbca9ca758b98d328069fc2dc3272393fdb6&r=7ae94799ece4bd283b2eb9da1f4ae0646c574a2f
19 | * Found by: http://stackoverflow.com/a/15367739
20 | */
21 |
22 | package org.passwordmaker.android.widgets;
23 |
24 | import android.animation.Animator;
25 | import android.animation.AnimatorListenerAdapter;
26 | import android.animation.ValueAnimator;
27 | import android.graphics.Rect;
28 | import android.view.MotionEvent;
29 | import android.view.VelocityTracker;
30 | import android.view.View;
31 | import android.view.ViewConfiguration;
32 | import android.view.ViewGroup;
33 | import android.widget.AbsListView;
34 | import android.widget.ListView;
35 | import org.jetbrains.annotations.NotNull;
36 |
37 | import java.util.ArrayList;
38 | import java.util.Collections;
39 | import java.util.List;
40 |
41 | /**
42 | * A {@link android.view.View.OnTouchListener} that makes the list items in a {@link ListView}
43 | * dismissible. {@link ListView} is given special treatment because by default it handles touches
44 | * for its list items... i.e. it's in charge of drawing the pressed state (the list selector),
45 | * handling list item clicks, etc.
46 | *
47 | *
After creating the listener, the caller should also call {@link
48 | * ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}, passing in the scroll
49 | * listener returned by {@link #makeScrollListener()}. If a scroll listener is already assigned, the
50 | * caller should still pass scroll changes through to this listener. This will ensure that this
51 | * {@link SwipeDismissListViewTouchListener} is paused during list view scrolling.
This class Requires API level 12 or later due to use of {@link
72 | * android.view.ViewPropertyAnimator}.
73 | */
74 | public class SwipeDismissListViewTouchListener implements View.OnTouchListener {
75 | // Cached ViewConfiguration and system-wide constant values
76 | private final int mSlop;
77 | private final int mMinFlingVelocity;
78 | private final int mMaxFlingVelocity;
79 | private final long mAnimationTime;
80 |
81 | // Fixed properties
82 | private final ListView mListView;
83 | private final DismissCallbacks mCallbacks;
84 | private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
85 |
86 | // Transient properties
87 | private final List mPendingDismisses = new ArrayList();
88 | private int mDismissAnimationRefCount = 0;
89 | private float mDownX;
90 | private boolean mSwiping;
91 | private VelocityTracker mVelocityTracker;
92 | private int mDownPosition;
93 | private View mDownView;
94 | private boolean mPaused;
95 |
96 | /**
97 | * The callback interface used by {@link SwipeDismissListViewTouchListener} to inform its client
98 | * about a successful dismissal of one or more list item positions.
99 | */
100 | @SuppressWarnings("SameReturnValue")
101 | public interface DismissCallbacks {
102 | /**
103 | * Called to determine whether the given position can be dismissed.
104 | */
105 | boolean canDismiss(int position);
106 |
107 | /**
108 | * Called when the user has indicated they she would like to dismiss one or more list item
109 | * positions.
110 | *
111 | * @param listView The originating {@link ListView}.
112 | * @param reverseSortedPositions An array of positions to dismiss, sorted in descending
113 | * order for convenience.
114 | */
115 | void onDismiss(ListView listView, int[] reverseSortedPositions);
116 | }
117 |
118 | /**
119 | * Constructs a new swipe-to-dismiss touch listener for the given list view.
120 | *
121 | * @param listView The list view whose items should be dismissible.
122 | * @param callbacks The callback to trigger when the user has indicated that she would like to
123 | * dismiss one or more list items.
124 | */
125 | public SwipeDismissListViewTouchListener(ListView listView, DismissCallbacks callbacks) {
126 | ViewConfiguration vc = ViewConfiguration.get(listView.getContext());
127 | mSlop = vc.getScaledTouchSlop();
128 | mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
129 | mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
130 | mAnimationTime = listView.getContext().getResources().getInteger(
131 | android.R.integer.config_shortAnimTime);
132 | mListView = listView;
133 | mCallbacks = callbacks;
134 | }
135 |
136 | /**
137 | * Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
138 | *
139 | * @param enabled Whether or not to watch for gestures.
140 | */
141 | protected void setEnabled(boolean enabled) {
142 | mPaused = !enabled;
143 | }
144 |
145 | /**
146 | * Returns an {@link android.widget.AbsListView.OnScrollListener} to be added to the {@link
147 | * ListView} using {@link ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}.
148 | * If a scroll listener is already assigned, the caller should still pass scroll changes through
149 | * to this listener. This will ensure that this {@link SwipeDismissListViewTouchListener} is
150 | * paused during list view scrolling.
151 | *
152 | * @see SwipeDismissListViewTouchListener
153 | */
154 | public AbsListView.OnScrollListener makeScrollListener() {
155 | return new AbsListView.OnScrollListener() {
156 | @Override
157 | public void onScrollStateChanged(AbsListView absListView, int scrollState) {
158 | setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
159 | }
160 |
161 | @Override
162 | public void onScroll(AbsListView absListView, int i, int i1, int i2) {
163 | }
164 | };
165 | }
166 |
167 | /**
168 | * Manually cause the item at the given position to be dismissed (trigger the dismiss
169 | * animation).
170 | */
171 | public void dismiss(int position) {
172 | dismiss(getViewForPosition(position), position, true);
173 | }
174 |
175 | @Override
176 | public boolean onTouch(View view, MotionEvent motionEvent) {
177 | if (mViewWidth < 2) {
178 | mViewWidth = mListView.getWidth();
179 | }
180 |
181 | switch (motionEvent.getActionMasked()) {
182 | case MotionEvent.ACTION_DOWN: {
183 | if (mPaused) {
184 | return false;
185 | }
186 |
187 | // TODO: ensure this is a finger, and set a flag
188 |
189 | // Find the child view that was touched (perform a hit test)
190 | Rect rect = new Rect();
191 | int childCount = mListView.getChildCount();
192 | int[] listViewCoords = new int[2];
193 | mListView.getLocationOnScreen(listViewCoords);
194 | int x = (int) motionEvent.getRawX() - listViewCoords[0];
195 | int y = (int) motionEvent.getRawY() - listViewCoords[1];
196 | View child;
197 | for (int i = 0; i < childCount; i++) {
198 | child = mListView.getChildAt(i);
199 | child.getHitRect(rect);
200 | if (rect.contains(x, y)) {
201 | mDownView = child;
202 | break;
203 | }
204 | }
205 |
206 | if (mDownView != null) {
207 | mDownX = motionEvent.getRawX();
208 | mDownPosition = mListView.getPositionForView(mDownView);
209 | if (mCallbacks.canDismiss(mDownPosition)) {
210 | mVelocityTracker = VelocityTracker.obtain();
211 | mVelocityTracker.addMovement(motionEvent);
212 | } else {
213 | mDownView = null;
214 | }
215 | }
216 | view.onTouchEvent(motionEvent);
217 | return true;
218 | }
219 |
220 | case MotionEvent.ACTION_UP: {
221 | if (mVelocityTracker == null) {
222 | break;
223 | }
224 |
225 | float deltaX = motionEvent.getRawX() - mDownX;
226 | mVelocityTracker.addMovement(motionEvent);
227 | mVelocityTracker.computeCurrentVelocity(1000);
228 | float velocityX = mVelocityTracker.getXVelocity();
229 | float absVelocityX = Math.abs(velocityX);
230 | float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
231 | boolean dismiss = false;
232 | boolean dismissRight = false;
233 | if (Math.abs(deltaX) > mViewWidth / 2) {
234 | dismiss = true;
235 | dismissRight = deltaX > 0;
236 | } else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
237 | && absVelocityY < absVelocityX) {
238 | // dismiss only if flinging in the same direction as dragging
239 | dismiss = (velocityX < 0) == (deltaX < 0);
240 | dismissRight = mVelocityTracker.getXVelocity() > 0;
241 | }
242 | if (dismiss) {
243 | // dismiss
244 | dismiss(mDownView, mDownPosition, dismissRight);
245 | } else {
246 | // cancel
247 | mDownView.animate()
248 | .translationX(0)
249 | .alpha(1)
250 | .setDuration(mAnimationTime)
251 | .setListener(null);
252 | }
253 | mVelocityTracker.recycle();
254 | mVelocityTracker = null;
255 | mDownX = 0;
256 | mDownView = null;
257 | mDownPosition = ListView.INVALID_POSITION;
258 | mSwiping = false;
259 | break;
260 | }
261 |
262 | case MotionEvent.ACTION_CANCEL: {
263 | if (mVelocityTracker == null) {
264 | break;
265 | }
266 |
267 | if (mDownView != null) {
268 | // cancel
269 | mDownView.animate()
270 | .translationX(0)
271 | .alpha(1)
272 | .setDuration(mAnimationTime)
273 | .setListener(null);
274 | }
275 | mVelocityTracker.recycle();
276 | mVelocityTracker = null;
277 | mDownX = 0;
278 | mDownView = null;
279 | mDownPosition = ListView.INVALID_POSITION;
280 | mSwiping = false;
281 | break;
282 | }
283 |
284 | case MotionEvent.ACTION_MOVE: {
285 | if (mVelocityTracker == null || mPaused) {
286 | break;
287 | }
288 |
289 | mVelocityTracker.addMovement(motionEvent);
290 | float deltaX = motionEvent.getRawX() - mDownX;
291 | if (Math.abs(deltaX) > mSlop) {
292 | mSwiping = true;
293 | mListView.requestDisallowInterceptTouchEvent(true);
294 |
295 | // Cancel ListView's touch (un-highlighting the item)
296 | MotionEvent cancelEvent = MotionEvent.obtain(motionEvent);
297 | cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
298 | (motionEvent.getActionIndex()
299 | << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
300 | mListView.onTouchEvent(cancelEvent);
301 | cancelEvent.recycle();
302 | }
303 |
304 | if (mSwiping) {
305 | mDownView.setTranslationX(deltaX);
306 | mDownView.setAlpha(Math.max(0.15f, Math.min(1f,
307 | 1f - 2f * Math.abs(deltaX) / mViewWidth)));
308 | return true;
309 | }
310 | break;
311 | }
312 | }
313 | return false;
314 | }
315 |
316 | private void dismiss(final View view, final int position, boolean dismissRight) {
317 | ++mDismissAnimationRefCount;
318 | if (view == null) {
319 | // No view, shortcut to calling onDismiss to let it deal with adapter
320 | // updates and all that.
321 | mCallbacks.onDismiss(mListView, new int[] { position });
322 | return;
323 | }
324 |
325 | view.animate()
326 | .translationX(dismissRight ? mViewWidth : -mViewWidth)
327 | .alpha(0)
328 | .setDuration(mAnimationTime)
329 | .setListener(new AnimatorListenerAdapter() {
330 | @Override
331 | public void onAnimationEnd(Animator animation) {
332 | performDismiss(view, position);
333 | }
334 | });
335 | }
336 |
337 | private View getViewForPosition(int position) {
338 | int index = position
339 | - (mListView.getFirstVisiblePosition() - mListView.getHeaderViewsCount());
340 | return (index >= 0 && index < mListView.getChildCount())
341 | ? mListView.getChildAt(index)
342 | : null;
343 | }
344 |
345 | class PendingDismissData implements Comparable {
346 | public final int position;
347 | public final View view;
348 |
349 | public PendingDismissData(int position, View view) {
350 | this.position = position;
351 | this.view = view;
352 | }
353 |
354 | @Override
355 | public int compareTo(@NotNull PendingDismissData other) {
356 | // Sort by descending position
357 | return other.position - position;
358 | }
359 | }
360 |
361 | private void performDismiss(final View dismissView, final int dismissPosition) {
362 | // Animate the dismissed list item to zero-height and fire the dismiss callback when
363 | // all dismissed list item animations have completed. This triggers layout on each animation
364 | // frame; in the future we may want to do something smarter and more performant.
365 |
366 | final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
367 | final int originalHeight = dismissView.getHeight();
368 |
369 | ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
370 |
371 | animator.addListener(new AnimatorListenerAdapter() {
372 | @Override
373 | public void onAnimationEnd(Animator animation) {
374 | --mDismissAnimationRefCount;
375 | if (mDismissAnimationRefCount == 0) {
376 | // No active animations, process all pending dismisses.
377 | // Sort by descending position
378 | Collections.sort(mPendingDismisses);
379 |
380 | int[] dismissPositions = new int[mPendingDismisses.size()];
381 | for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
382 | dismissPositions[i] = mPendingDismisses.get(i).position;
383 | }
384 | mCallbacks.onDismiss(mListView, dismissPositions);
385 |
386 | ViewGroup.LayoutParams lp;
387 | for (PendingDismissData pendingDismiss : mPendingDismisses) {
388 | // Reset view presentation
389 | pendingDismiss.view.setAlpha(1f);
390 | pendingDismiss.view.setTranslationX(0);
391 | lp = pendingDismiss.view.getLayoutParams();
392 | lp.height = originalHeight;
393 | pendingDismiss.view.setLayoutParams(lp);
394 | }
395 |
396 | mPendingDismisses.clear();
397 | }
398 | }
399 | });
400 |
401 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
402 | @Override
403 | public void onAnimationUpdate(ValueAnimator valueAnimator) {
404 | lp.height = (Integer) valueAnimator.getAnimatedValue();
405 | dismissView.setLayoutParams(lp);
406 | }
407 | });
408 |
409 | mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView));
410 | animator.start();
411 | }
412 | }
--------------------------------------------------------------------------------