First working Master Password for Android, yay!
This commit is contained in:
parent
89145d6e13
commit
65cef6d8ed
@ -1,5 +1,6 @@
|
|||||||
package com.lyndir.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -93,14 +94,12 @@ public enum MPElementType {
|
|||||||
*
|
*
|
||||||
* @return All types that support the given class.
|
* @return All types that support the given class.
|
||||||
*/
|
*/
|
||||||
public static ImmutableSet<MPElementType> forClass(final MPElementTypeClass typeClass) {
|
public static ImmutableList<MPElementType> forClass(final MPElementTypeClass typeClass) {
|
||||||
|
|
||||||
ImmutableSet.Builder<MPElementType> types = ImmutableSet.builder();
|
ImmutableList.Builder<MPElementType> types = ImmutableList.builder();
|
||||||
for (final MPElementType type : values()) {
|
for (final MPElementType type : values())
|
||||||
if (type.getTypeClass() == typeClass) {
|
if (type.getTypeClass() == typeClass)
|
||||||
types.add( type );
|
types.add( type );
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return types.build();
|
return types.build();
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,14 @@
|
|||||||
android:versionName="GIT-SNAPSHOT">
|
android:versionName="GIT-SNAPSHOT">
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="11"
|
android:minSdkVersion="14"
|
||||||
android:targetSdkVersion="19" />
|
android:targetSdkVersion="19" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:icon="@drawable/icon"
|
android:icon="@drawable/icon"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:allowBackup="true">
|
android:allowBackup="true">
|
||||||
<activity android:name=".UsersActivity">
|
<activity android:name=".EmergencyActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -46,6 +46,12 @@
|
|||||||
<artifactId>butterknife</artifactId>
|
<artifactId>butterknife</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-android</artifactId>
|
||||||
|
<version>1.7.7</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- clone https://github.com/mosabua/maven-android-sdk-deployer.git
|
<!-- clone https://github.com/mosabua/maven-android-sdk-deployer.git
|
||||||
run mvn install -P 4.4 -->
|
run mvn install -P 4.4 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 3.6 KiB |
@ -0,0 +1,88 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:background="@drawable/background">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/userNameField"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text|textCapWords|textPersonName"
|
||||||
|
android:hint="@string/userName.hint"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="26sp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/masterPasswordField"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text|textPassword"
|
||||||
|
android:hint="@string/masterPassword.hint"
|
||||||
|
android:password="true"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/double_"
|
||||||
|
android:contentDescription="@string/empty" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/siteNameField"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text|textNoSuggestions|textUri"
|
||||||
|
android:hint="@string/siteName.hint"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="26sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sitePasswordField"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="32sp"
|
||||||
|
android:text="LuxdZozvDuma4[" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/typeField"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
<NumberPicker
|
||||||
|
android:id="@+id/counterField"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
@ -2,4 +2,8 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Master Password</string>
|
<string name="app_name">Master Password</string>
|
||||||
<string name="avatar">User Avatar</string>
|
<string name="avatar">User Avatar</string>
|
||||||
|
<string name="siteName.hint">Site Name</string>
|
||||||
|
<string name="userName.hint">Your Name</string>
|
||||||
|
<string name="masterPassword.hint">Your Master Password</string>
|
||||||
|
<string name="empty" />
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -0,0 +1,255 @@
|
|||||||
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.text.Editable;
|
||||||
|
import android.text.TextWatcher;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.*;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.InjectView;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.util.concurrent.*;
|
||||||
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
|
|
||||||
|
public class EmergencyActivity extends Activity {
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
|
private static final Logger logger = Logger.get( EmergencyActivity.class );
|
||||||
|
|
||||||
|
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
|
||||||
|
private final ValueChangedListener updateMasterKey = new ValueChangedListener() {
|
||||||
|
@Override
|
||||||
|
void update() {
|
||||||
|
updateMasterKey();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private final ValueChangedListener updateSitePassword = new ValueChangedListener() {
|
||||||
|
@Override
|
||||||
|
void update() {
|
||||||
|
updateSitePassword();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private ListenableFuture<byte[]> masterKeyFuture;
|
||||||
|
|
||||||
|
@InjectView(R.id.progressView)
|
||||||
|
ProgressBar progressView;
|
||||||
|
|
||||||
|
@InjectView(R.id.userNameField)
|
||||||
|
EditText userNameField;
|
||||||
|
|
||||||
|
@InjectView(R.id.masterPasswordField)
|
||||||
|
EditText masterPasswordField;
|
||||||
|
|
||||||
|
@InjectView(R.id.siteNameField)
|
||||||
|
EditText siteNameField;
|
||||||
|
|
||||||
|
@InjectView(R.id.typeField)
|
||||||
|
Spinner typeField;
|
||||||
|
|
||||||
|
@InjectView(R.id.counterField)
|
||||||
|
NumberPicker counterField;
|
||||||
|
|
||||||
|
@InjectView(R.id.sitePasswordField)
|
||||||
|
TextView sitePasswordField;
|
||||||
|
|
||||||
|
private int hc_userName;
|
||||||
|
private int hc_masterPassword;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate( savedInstanceState );
|
||||||
|
Res.init( getResources() );
|
||||||
|
|
||||||
|
setContentView( R.layout.activity_emergency );
|
||||||
|
ButterKnife.inject( this );
|
||||||
|
|
||||||
|
userNameField.setOnFocusChangeListener( updateMasterKey );
|
||||||
|
masterPasswordField.setOnFocusChangeListener( updateMasterKey );
|
||||||
|
siteNameField.addTextChangedListener( updateSitePassword );
|
||||||
|
typeField.setOnItemSelectedListener( updateSitePassword );
|
||||||
|
counterField.setOnValueChangedListener( updateSitePassword );
|
||||||
|
|
||||||
|
userNameField.setTypeface( Res.exo_Thin );
|
||||||
|
userNameField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
|
masterPasswordField.setTypeface( Res.sourceCodePro_ExtraLight );
|
||||||
|
masterPasswordField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
|
siteNameField.setTypeface( Res.exo_Regular );
|
||||||
|
siteNameField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
|
sitePasswordField.setTypeface( Res.sourceCodePro_Black );
|
||||||
|
sitePasswordField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
|
|
||||||
|
typeField.setAdapter( new ArrayAdapter<>( this, R.layout.type_item, MPElementType.forClass( MPElementTypeClass.Generated ) ) );
|
||||||
|
typeField.setSelection( MPElementType.GeneratedLong.ordinal() );
|
||||||
|
|
||||||
|
counterField.setMinValue( 1 );
|
||||||
|
counterField.setMaxValue( Integer.MAX_VALUE );
|
||||||
|
counterField.setWrapSelectorWheel( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
userNameField.setText( getPreferences( MODE_PRIVATE ).getString( "userName", "" ) );
|
||||||
|
masterPasswordField.requestFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
synchronized (this) {
|
||||||
|
hc_userName = hc_masterPassword = 0;
|
||||||
|
if (masterKeyFuture != null) {
|
||||||
|
masterKeyFuture.cancel( true );
|
||||||
|
masterKeyFuture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void updateMasterKey() {
|
||||||
|
final String userName = userNameField.getText().toString();
|
||||||
|
final String masterPassword = masterPasswordField.getText().toString();
|
||||||
|
if (userName.hashCode() == hc_userName && masterPassword.hashCode() == hc_masterPassword)
|
||||||
|
return;
|
||||||
|
hc_userName = userName.hashCode();
|
||||||
|
hc_masterPassword = masterPassword.hashCode();
|
||||||
|
|
||||||
|
SharedPreferences.Editor pref = getPreferences( MODE_PRIVATE ).edit();
|
||||||
|
pref.putString( "userName", userName );
|
||||||
|
pref.commit();
|
||||||
|
|
||||||
|
if (masterKeyFuture != null)
|
||||||
|
masterKeyFuture.cancel( true );
|
||||||
|
|
||||||
|
if (userName.isEmpty() || masterPassword.isEmpty()) {
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressView.setVisibility( View.VISIBLE );
|
||||||
|
(masterKeyFuture = executor.submit( new Callable<byte[]>() {
|
||||||
|
@Override
|
||||||
|
public byte[] call()
|
||||||
|
throws Exception {
|
||||||
|
try {
|
||||||
|
return MasterPassword.keyForPassword( masterPassword, userName );
|
||||||
|
}
|
||||||
|
catch (RuntimeException e) {
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
logger.err( e, "While generating master key." );
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} )).addListener( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
runOnUiThread( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
updateSitePassword();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}, executor );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSitePassword() {
|
||||||
|
final String siteName = siteNameField.getText().toString();
|
||||||
|
final MPElementType type = (MPElementType) typeField.getSelectedItem();
|
||||||
|
final int counter = counterField.getValue();
|
||||||
|
|
||||||
|
if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progressView.setVisibility( View.VISIBLE );
|
||||||
|
executor.submit( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
final String sitePassword = MasterPassword.generateContent( type, siteName, masterKeyFuture.get(), counter );
|
||||||
|
|
||||||
|
runOnUiThread( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
sitePasswordField.setText( sitePassword );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
catch (InterruptedException ignored) {
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
}
|
||||||
|
catch (ExecutionException e) {
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
logger.err( e, "While generating site password." );
|
||||||
|
throw Throwables.propagate( e );
|
||||||
|
}
|
||||||
|
catch (RuntimeException e) {
|
||||||
|
sitePasswordField.setText( "" );
|
||||||
|
progressView.setVisibility( View.INVISIBLE );
|
||||||
|
logger.err( e, "While generating site password." );
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class ValueChangedListener
|
||||||
|
implements TextWatcher, NumberPicker.OnValueChangeListener, AdapterView.OnItemSelectedListener, View.OnFocusChangeListener {
|
||||||
|
|
||||||
|
abstract void update();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterTextChanged(final Editable s) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onValueChange(final NumberPicker picker, final int oldVal, final int newVal) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(final AdapterView<?> parent, final View view, final int position, final long id) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(final AdapterView<?> parent) {
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFocusChange(final View v, final boolean hasFocus) {
|
||||||
|
if (!hasFocus)
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Typeface;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2014-08-25
|
||||||
|
*/
|
||||||
|
public class Res {
|
||||||
|
|
||||||
|
public static Typeface sourceCodePro_Black;
|
||||||
|
public static Typeface sourceCodePro_ExtraLight;
|
||||||
|
public static Typeface exo_Bold;
|
||||||
|
public static Typeface exo_ExtraBold;
|
||||||
|
public static Typeface exo_Regular;
|
||||||
|
public static Typeface exo_Thin;
|
||||||
|
|
||||||
|
private static boolean initialized;
|
||||||
|
|
||||||
|
public static void init(Resources resources) {
|
||||||
|
|
||||||
|
if (initialized)
|
||||||
|
return;
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
sourceCodePro_Black = Typeface.createFromAsset( resources.getAssets(), "SourceCodePro-Black.otf" );
|
||||||
|
sourceCodePro_ExtraLight = Typeface.createFromAsset( resources.getAssets(), "SourceCodePro-ExtraLight.otf" );
|
||||||
|
exo_Bold = Typeface.createFromAsset( resources.getAssets(), "Exo2.0-Bold.otf" );
|
||||||
|
exo_ExtraBold = Typeface.createFromAsset( resources.getAssets(), "Exo2.0-ExtraBold.otf" );
|
||||||
|
exo_Regular = Typeface.createFromAsset( resources.getAssets(), "Exo2.0-Regular.otf" );
|
||||||
|
exo_Thin = Typeface.createFromAsset( resources.getAssets(), "Exo2.0-Thin.otf" );
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user