Re-organize the project into a better hierarchy.
13
platform-android/README
Normal file
@@ -0,0 +1,13 @@
|
||||
To build this module, please ensure you've done the following setup:
|
||||
|
||||
1. Installed the Android SDK and fully downloaded the Android SDK platform 21 in it.
|
||||
2. Set the environment variable ANDROID_HOME in your shell or in ~/.mavenrc to point to the root of your Android SDK install.
|
||||
3. Installed the Android SDK into your Maven's local repository.
|
||||
3a. Clone the maven-android-sdk-deployer available from here: https://github.com/mosabua/maven-android-sdk-deployer.git
|
||||
3b. In the root of this project, run: mvn install -P 5.0
|
||||
|
||||
To build this module:
|
||||
|
||||
1. Build the parent, by going into 'MasterPassword/Java' and running: mvn clean install
|
||||
2. Build this module, by going into 'MasterPassword/Java/masterpassword-android' and running: mvn clean install
|
||||
3. You can then find the APK in: 'MasterPassword/Java/masterpassword-android/target'
|
44
platform-android/build.gradle
Normal file
@@ -0,0 +1,44 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.0"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
}
|
||||
defaultConfig {
|
||||
applicationId "com.lyndir.masterpassword"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 25
|
||||
versionCode 20401
|
||||
versionName "2.4.1"
|
||||
}
|
||||
// release with: STORE_PW=$(mpw masterpassword.keystore) KEY_PW=$(mpw masterpassword-android) gradle assembleRelease
|
||||
signingConfigs {
|
||||
release {
|
||||
storeFile file('masterpassword.keystore')
|
||||
storePassword System.getenv('STORE_PW')
|
||||
|
||||
keyAlias 'masterpassword-android'
|
||||
keyPassword System.getenv('KEY_PW')
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
signingConfig signingConfigs.release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project( ':masterpassword-algorithm' )
|
||||
compile project( ':masterpassword-tests' )
|
||||
|
||||
// Android dependencies
|
||||
compile 'org.slf4j:slf4j-android:1.7.13-underscore'
|
||||
compile 'com.jakewharton:butterknife:8.5.1'
|
||||
annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1'
|
||||
compile files('libs/scrypt-1.4.0-native.jar')
|
||||
}
|
BIN
platform-android/libs/scrypt-1.4.0-native.jar
Normal file
1
platform-android/masterpassword.jks
Symbolic link
@@ -0,0 +1 @@
|
||||
/Users/lhunath/annex/secret/release-com.lyndir.masterpassword.jks
|
1
platform-android/masterpassword.keystore
Symbolic link
@@ -0,0 +1 @@
|
||||
/Users/lhunath/annex/secret/masterpassword.keystore
|
145
platform-android/pom.xml
Normal file
@@ -0,0 +1,145 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- PROJECT METADATA -->
|
||||
<parent>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<name>Master Password Android</name>
|
||||
<description>An Android application to the Master Password algorithm</description>
|
||||
|
||||
<artifactId>masterpassword-android</artifactId>
|
||||
<packaging>apk</packaging>
|
||||
|
||||
<!-- BUILD CONFIGURATION -->
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
|
||||
<artifactId>android-maven-plugin</artifactId>
|
||||
|
||||
<configuration>
|
||||
<zipalign>
|
||||
<verbose>true</verbose>
|
||||
<skip>false</skip>
|
||||
</zipalign>
|
||||
<sdk>
|
||||
<platform>21</platform>
|
||||
</sdk>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>release</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
|
||||
<artifactId>android-maven-plugin</artifactId>
|
||||
|
||||
<configuration>
|
||||
<sign>
|
||||
<debug>false</debug>
|
||||
</sign>
|
||||
</configuration>
|
||||
|
||||
<executions>
|
||||
<execution>
|
||||
<id>manifest-update</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>manifest-update</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<manifestVersionCodeUpdateFromVersion>true</manifestVersionCodeUpdateFromVersion>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jarsigner-plugin</artifactId>
|
||||
<version>1.4</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>signing</id>
|
||||
<goals>
|
||||
<goal>sign</goal>
|
||||
</goals>
|
||||
<phase>package</phase>
|
||||
<inherited>true</inherited>
|
||||
<configuration>
|
||||
<archiveDirectory />
|
||||
<includes>
|
||||
<include>target/*.apk</include>
|
||||
</includes>
|
||||
<keystore>release.jks</keystore>
|
||||
<storepass>${env.PASSWORD}</storepass>
|
||||
<keypass>${env.PASSWORD}</keypass>
|
||||
<alias>masterpassword-android</alias>
|
||||
<arguments>
|
||||
<argument>-sigalg</argument><argument>MD5withRSA</argument>
|
||||
<argument>-digestalg</argument><argument>SHA1</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<!-- DEPENDENCY MANAGEMENT -->
|
||||
<dependencies>
|
||||
|
||||
<!-- PROJECT REFERENCES -->
|
||||
<dependency>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword-algorithm</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword-tests</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EXTERNAL DEPENDENCIES -->
|
||||
<dependency>
|
||||
<groupId>com.jakewharton</groupId>
|
||||
<artifactId>butterknife</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-android</artifactId>
|
||||
<version>1.7.13-underscore</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>android</groupId>
|
||||
<artifactId>android</artifactId>
|
||||
<version>5.0.1_r2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.lambdaworks</groupId>
|
||||
<artifactId>scrypt</artifactId>
|
||||
<version>1.4.0-android</version>
|
||||
<type>jar</type>
|
||||
<classifier>native</classifier>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
5
platform-android/proguard.cfg
Normal file
@@ -0,0 +1,5 @@
|
||||
-ignorewarnings
|
||||
-dontoptimize
|
||||
-dontobfuscate
|
||||
-dontskipnonpubliclibraryclasses
|
||||
-dontskipnonpubliclibraryclassmembers
|
23
platform-android/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.lyndir.masterpassword">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="@string/app_name">
|
||||
<activity
|
||||
android:name=".EmergencyActivity"
|
||||
android:theme="@style/MPTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".TestActivity"
|
||||
android:theme="@style/MPTheme" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
BIN
platform-android/src/main/assets/Exo2.0-Bold.otf
Normal file
BIN
platform-android/src/main/assets/Exo2.0-ExtraBold.otf
Normal file
BIN
platform-android/src/main/assets/Exo2.0-Regular.otf
Normal file
BIN
platform-android/src/main/assets/Exo2.0-Thin.otf
Normal file
BIN
platform-android/src/main/assets/SourceCodePro-Black.otf
Normal file
BIN
platform-android/src/main/assets/SourceCodePro-ExtraLight.otf
Normal file
@@ -0,0 +1,435 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.*;
|
||||
import android.content.ClipboardManager;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.*;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.*;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
public class EmergencyActivity extends Activity {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( EmergencyActivity.class );
|
||||
private static final ClipData EMPTY_CLIP = new ClipData( new ClipDescription( "", new String[0] ), new ClipData.Item( "" ) );
|
||||
private static final int PASSWORD_NOTIFICATION = 0;
|
||||
|
||||
private final Preferences preferences = Preferences.get( this );
|
||||
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
|
||||
private final ImmutableList<MPSiteType> allSiteTypes = ImmutableList.copyOf( MPSiteType.forClass( MPSiteTypeClass.Generated ) );
|
||||
private final ImmutableList<MasterKey.Version> allVersions = ImmutableList.copyOf( MasterKey.Version.values() );
|
||||
|
||||
private ListenableFuture<MasterKey> masterKeyFuture;
|
||||
|
||||
@BindView(R.id.progressView)
|
||||
ProgressBar progressView;
|
||||
|
||||
@BindView(R.id.fullNameField)
|
||||
EditText fullNameField;
|
||||
|
||||
@BindView(R.id.masterPasswordField)
|
||||
EditText masterPasswordField;
|
||||
|
||||
@BindView(R.id.siteNameField)
|
||||
EditText siteNameField;
|
||||
|
||||
@BindView(R.id.siteTypeButton)
|
||||
Button siteTypeButton;
|
||||
|
||||
@BindView(R.id.counterField)
|
||||
Button siteCounterButton;
|
||||
|
||||
@BindView(R.id.siteVersionButton)
|
||||
Button siteVersionButton;
|
||||
|
||||
@BindView(R.id.sitePasswordField)
|
||||
Button sitePasswordField;
|
||||
|
||||
@BindView(R.id.sitePasswordTip)
|
||||
TextView sitePasswordTip;
|
||||
|
||||
@BindView(R.id.rememberFullNameField)
|
||||
CheckBox rememberFullNameField;
|
||||
|
||||
@BindView(R.id.rememberPasswordField)
|
||||
CheckBox forgetPasswordField;
|
||||
|
||||
@BindView(R.id.maskPasswordField)
|
||||
CheckBox maskPasswordField;
|
||||
|
||||
private int id_userName;
|
||||
private int id_masterPassword;
|
||||
private int id_version;
|
||||
private String sitePassword;
|
||||
|
||||
public static void start(Context context) {
|
||||
context.startActivity( new Intent( context, EmergencyActivity.class ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate( savedInstanceState );
|
||||
Res.init( getResources() );
|
||||
|
||||
getWindow().setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE );
|
||||
setContentView( R.layout.activity_emergency );
|
||||
ButterKnife.bind( this );
|
||||
|
||||
fullNameField.setOnFocusChangeListener( new ValueChangedListener() {
|
||||
@Override
|
||||
void update() {
|
||||
updateMasterKey();
|
||||
}
|
||||
} );
|
||||
masterPasswordField.setOnFocusChangeListener( new ValueChangedListener() {
|
||||
@Override
|
||||
void update() {
|
||||
updateMasterKey();
|
||||
}
|
||||
} );
|
||||
siteNameField.addTextChangedListener( new ValueChangedListener() {
|
||||
@Override
|
||||
void update() {
|
||||
siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) );
|
||||
updateSitePassword();
|
||||
}
|
||||
} );
|
||||
siteTypeButton.setOnClickListener( new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
@SuppressWarnings("SuspiciousMethodCalls")
|
||||
MPSiteType siteType =
|
||||
allSiteTypes.get( (allSiteTypes.indexOf( siteTypeButton.getTag() ) + 1) % allSiteTypes.size() );
|
||||
preferences.setDefaultSiteType( siteType );
|
||||
siteTypeButton.setTag( siteType );
|
||||
siteTypeButton.setText( siteType.getShortName() );
|
||||
updateSitePassword();
|
||||
}
|
||||
} );
|
||||
siteCounterButton.setOnClickListener( new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
UnsignedInteger counter =
|
||||
UnsignedInteger.valueOf( siteCounterButton.getText().toString() ).plus( UnsignedInteger.ONE );
|
||||
siteCounterButton.setText( MessageFormat.format( "{0}", counter ) );
|
||||
updateSitePassword();
|
||||
}
|
||||
} );
|
||||
siteVersionButton.setOnClickListener( new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final View v) {
|
||||
@SuppressWarnings("SuspiciousMethodCalls")
|
||||
MasterKey.Version siteVersion =
|
||||
allVersions.get( (allVersions.indexOf( siteVersionButton.getTag() ) + 1) % allVersions.size() );
|
||||
preferences.setDefaultVersion( siteVersion );
|
||||
siteVersionButton.setTag( siteVersion );
|
||||
siteVersionButton.setText( siteVersion.name() );
|
||||
updateMasterKey();
|
||||
}
|
||||
} );
|
||||
sitePasswordField.addTextChangedListener( new ValueChangedListener() {
|
||||
@Override
|
||||
void update() {
|
||||
boolean noPassword = TextUtils.isEmpty( sitePasswordField.getText() );
|
||||
sitePasswordTip.setVisibility( noPassword? View.INVISIBLE: View.VISIBLE );
|
||||
|
||||
if (noPassword)
|
||||
sitePassword = null;
|
||||
}
|
||||
} );
|
||||
|
||||
fullNameField.setTypeface( Res.exo_Thin );
|
||||
fullNameField.setPaintFlags( fullNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||
masterPasswordField.setTypeface( Res.sourceCodePro_ExtraLight );
|
||||
masterPasswordField.setPaintFlags( masterPasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||
siteNameField.setTypeface( Res.exo_Regular );
|
||||
siteNameField.setPaintFlags( siteNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||
sitePasswordField.setTypeface( Res.sourceCodePro_Black );
|
||||
sitePasswordField.setPaintFlags( sitePasswordField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||
|
||||
rememberFullNameField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
||||
preferences.setRememberFullName( isChecked );
|
||||
if (isChecked)
|
||||
preferences.setFullName( fullNameField.getText().toString() );
|
||||
else
|
||||
preferences.setFullName( null );
|
||||
}
|
||||
} );
|
||||
forgetPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
||||
preferences.setForgetPassword( isChecked );
|
||||
}
|
||||
} );
|
||||
maskPasswordField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
||||
preferences.setMaskPassword( isChecked );
|
||||
sitePasswordField.setTransformationMethod( isChecked? new PasswordTransformationMethod(): null );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
|
||||
|
||||
fullNameField.setText( preferences.getFullName() );
|
||||
rememberFullNameField.setChecked( preferences.isRememberFullName() );
|
||||
forgetPasswordField.setChecked( preferences.isForgetPassword() );
|
||||
maskPasswordField.setChecked( preferences.isMaskPassword() );
|
||||
sitePasswordField.setTransformationMethod( preferences.isMaskPassword()? new PasswordTransformationMethod(): null );
|
||||
MPSiteType defaultSiteType = preferences.getDefaultSiteType();
|
||||
siteTypeButton.setTag( defaultSiteType );
|
||||
siteTypeButton.setText( defaultSiteType.getShortName() );
|
||||
MasterKey.Version defaultVersion = preferences.getDefaultVersion();
|
||||
siteVersionButton.setTag( defaultVersion );
|
||||
siteVersionButton.setText( defaultVersion.name() );
|
||||
siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) );
|
||||
|
||||
if (TextUtils.isEmpty( fullNameField.getText() ))
|
||||
fullNameField.requestFocus();
|
||||
else if (TextUtils.isEmpty( masterPasswordField.getText() ))
|
||||
masterPasswordField.requestFocus();
|
||||
else
|
||||
siteNameField.requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
if (preferences.isForgetPassword()) {
|
||||
synchronized (this) {
|
||||
id_userName = id_masterPassword = 0;
|
||||
if (masterKeyFuture != null) {
|
||||
masterKeyFuture.cancel( true );
|
||||
masterKeyFuture = null;
|
||||
}
|
||||
|
||||
masterPasswordField.setText( "" );
|
||||
}
|
||||
}
|
||||
|
||||
siteNameField.setText( "" );
|
||||
sitePasswordField.setText( "" );
|
||||
progressView.setVisibility( View.INVISIBLE );
|
||||
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
private synchronized void updateMasterKey() {
|
||||
final String fullName = fullNameField.getText().toString();
|
||||
final char[] masterPassword = masterPasswordField.getText().toString().toCharArray();
|
||||
final MasterKey.Version version = (MasterKey.Version) siteVersionButton.getTag();
|
||||
if (fullName.hashCode() == id_userName && Arrays.hashCode( masterPassword ) == id_masterPassword &&
|
||||
version.ordinal() == id_version && masterKeyFuture != null && !masterKeyFuture.isCancelled())
|
||||
return;
|
||||
|
||||
id_userName = fullName.hashCode();
|
||||
id_masterPassword = Arrays.hashCode( masterPassword );
|
||||
id_version = version.ordinal();
|
||||
|
||||
if (preferences.isRememberFullName())
|
||||
preferences.setFullName( fullName );
|
||||
|
||||
if (masterKeyFuture != null)
|
||||
masterKeyFuture.cancel( true );
|
||||
|
||||
if (fullName.isEmpty() || masterPassword.length == 0) {
|
||||
sitePasswordField.setText( "" );
|
||||
progressView.setVisibility( View.INVISIBLE );
|
||||
return;
|
||||
}
|
||||
|
||||
sitePasswordField.setText( "" );
|
||||
progressView.setVisibility( View.VISIBLE );
|
||||
(masterKeyFuture = executor.submit( new Callable<MasterKey>() {
|
||||
@Override
|
||||
public MasterKey call()
|
||||
throws Exception {
|
||||
try {
|
||||
return MasterKey.create( version, fullName, masterPassword );
|
||||
}
|
||||
catch (Exception 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 MPSiteType type = (MPSiteType) siteTypeButton.getTag();
|
||||
final UnsignedInteger counter = UnsignedInteger.valueOf( siteCounterButton.getText().toString() );
|
||||
|
||||
if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
|
||||
sitePasswordField.setText( "" );
|
||||
progressView.setVisibility( View.INVISIBLE );
|
||||
|
||||
if (masterKeyFuture == null)
|
||||
updateMasterKey();
|
||||
return;
|
||||
}
|
||||
|
||||
sitePasswordField.setText( "" );
|
||||
progressView.setVisibility( View.VISIBLE );
|
||||
executor.submit( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
sitePassword = masterKeyFuture.get().encode( siteName, type, counter, MPSiteVariant.Password, null );
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
public void integrityTests(View view) {
|
||||
if (masterKeyFuture != null) {
|
||||
masterKeyFuture.cancel( true );
|
||||
masterKeyFuture = null;
|
||||
}
|
||||
TestActivity.startNoSkip( this );
|
||||
}
|
||||
|
||||
public void copySitePassword(View view) {
|
||||
final String currentSitePassword = this.sitePassword;
|
||||
if (TextUtils.isEmpty( currentSitePassword ))
|
||||
return;
|
||||
|
||||
final ClipboardManager clipboardManager = (ClipboardManager) getSystemService( CLIPBOARD_SERVICE );
|
||||
final NotificationManager notificationManager = (NotificationManager) getSystemService( Context.NOTIFICATION_SERVICE );
|
||||
|
||||
String title = strf( "Password for %s", siteNameField.getText() );
|
||||
ClipDescription description = new ClipDescription( title, new String[]{ ClipDescription.MIMETYPE_TEXT_PLAIN } );
|
||||
clipboardManager.setPrimaryClip( new ClipData( description, new ClipData.Item( currentSitePassword ) ) );
|
||||
|
||||
Notification.Builder notificationBuilder = new Notification.Builder( this ).setContentTitle( title )
|
||||
.setContentText( "Paste the password into your app." )
|
||||
.setSmallIcon( R.drawable.icon )
|
||||
.setAutoCancel( true );
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
|
||||
notificationBuilder.setVisibility( Notification.VISIBILITY_SECRET )
|
||||
.setCategory( Notification.CATEGORY_RECOMMENDATION )
|
||||
.setLocalOnly( true );
|
||||
notificationManager.notify( PASSWORD_NOTIFICATION, notificationBuilder.build() );
|
||||
final Timer timer = new Timer();
|
||||
timer.schedule( new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
ClipData clip = clipboardManager.getPrimaryClip();
|
||||
for (int i = 0; i < clip.getItemCount(); ++i)
|
||||
if (currentSitePassword.equals( clip.getItemAt( i ).coerceToText( EmergencyActivity.this ) )) {
|
||||
clipboardManager.setPrimaryClip( EMPTY_CLIP );
|
||||
break;
|
||||
}
|
||||
notificationManager.cancel( PASSWORD_NOTIFICATION );
|
||||
timer.cancel();
|
||||
}
|
||||
}, 20000 );
|
||||
|
||||
Intent startMain = new Intent( Intent.ACTION_MAIN );
|
||||
startMain.addCategory( Intent.CATEGORY_HOME );
|
||||
startMain.setFlags( Intent.FLAG_ACTIVITY_NEW_TASK );
|
||||
startActivity( startMain );
|
||||
}
|
||||
|
||||
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,82 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2015-12-22
|
||||
*/
|
||||
public class MainThreadExecutor extends AbstractExecutorService {
|
||||
|
||||
private final Handler mHandler = new Handler( Looper.getMainLooper() );
|
||||
private final Set<Runnable> commands = Sets.newLinkedHashSet();
|
||||
private boolean shutdown;
|
||||
|
||||
@Override
|
||||
public void execute(final Runnable command) {
|
||||
if (shutdown)
|
||||
throw new RejectedExecutionException( "This executor has been shut down" );
|
||||
|
||||
synchronized (commands) {
|
||||
commands.add( command );
|
||||
|
||||
mHandler.post( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
synchronized (commands) {
|
||||
if (!commands.remove( command ))
|
||||
// Command was removed, not executing.
|
||||
return;
|
||||
}
|
||||
|
||||
command.run();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
shutdown = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Runnable> shutdownNow() {
|
||||
shutdown = true;
|
||||
mHandler.removeCallbacksAndMessages( null );
|
||||
|
||||
synchronized (commands) {
|
||||
ImmutableList<Runnable> pendingTasks = ImmutableList.copyOf( commands );
|
||||
commands.clear();
|
||||
commands.notify();
|
||||
return pendingTasks;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
return shutdown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
synchronized (commands) {
|
||||
return shutdown && commands.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitTermination(final long timeout, final TimeUnit unit)
|
||||
throws InterruptedException {
|
||||
if (isTerminated())
|
||||
return true;
|
||||
|
||||
commands.wait( unit.toMillis( timeout ) );
|
||||
return isTerminated();
|
||||
}
|
||||
}
|
@@ -0,0 +1,148 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2016-02-20
|
||||
*/
|
||||
public class Preferences {
|
||||
|
||||
private static final String PREF_TESTS_PASSED = "integrityTestsPassed";
|
||||
private static final String PREF_NATIVE_KDF = "nativeKDF";
|
||||
private static final String PREF_REMEMBER_FULL_NAME = "rememberFullName";
|
||||
private static final String PREF_FORGET_PASSWORD = "forgetPassword";
|
||||
private static final String PREF_MASK_PASSWORD = "maskPassword";
|
||||
private static final String PREF_FULL_NAME = "fullName";
|
||||
private static final String PREF_SITE_TYPE = "siteType";
|
||||
private static final String PREF_ALGORITHM_VERSION = "algorithmVersion";
|
||||
private static Preferences instance;
|
||||
|
||||
private Context context;
|
||||
@Nullable
|
||||
private SharedPreferences prefs;
|
||||
|
||||
public static synchronized Preferences get(final Context context) {
|
||||
if (instance == null)
|
||||
instance = new Preferences( context );
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
private Preferences(Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
private SharedPreferences prefs() {
|
||||
if (prefs == null)
|
||||
prefs = (context = context.getApplicationContext()).getSharedPreferences( getClass().getCanonicalName(), Context.MODE_PRIVATE );
|
||||
|
||||
return prefs;
|
||||
}
|
||||
|
||||
public boolean setNativeKDFEnabled(boolean enabled) {
|
||||
if (isAllowNativeKDF() == enabled)
|
||||
return false;
|
||||
|
||||
prefs().edit().putBoolean( PREF_NATIVE_KDF, enabled ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isAllowNativeKDF() {
|
||||
return prefs().getBoolean( PREF_NATIVE_KDF, MasterKey.isAllowNativeByDefault() );
|
||||
}
|
||||
|
||||
public boolean setTestsPassed(final Set<String> value) {
|
||||
if (Sets.symmetricDifference( getTestsPassed(), value ).isEmpty())
|
||||
return false;
|
||||
|
||||
prefs().edit().putStringSet( PREF_TESTS_PASSED, value ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
public Set<String> getTestsPassed() {
|
||||
return prefs().getStringSet( PREF_TESTS_PASSED, ImmutableSet.<String>of() );
|
||||
}
|
||||
|
||||
public boolean setRememberFullName(boolean enabled) {
|
||||
if (isRememberFullName() == enabled)
|
||||
return false;
|
||||
|
||||
prefs().edit().putBoolean( PREF_REMEMBER_FULL_NAME, enabled ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isRememberFullName() {
|
||||
return prefs().getBoolean( PREF_REMEMBER_FULL_NAME, false );
|
||||
}
|
||||
|
||||
public boolean setForgetPassword(boolean enabled) {
|
||||
if (isForgetPassword() == enabled)
|
||||
return false;
|
||||
|
||||
prefs().edit().putBoolean( PREF_FORGET_PASSWORD, enabled ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isForgetPassword() {
|
||||
return prefs().getBoolean( PREF_FORGET_PASSWORD, false );
|
||||
}
|
||||
|
||||
public boolean setMaskPassword(boolean enabled) {
|
||||
if (isMaskPassword() == enabled)
|
||||
return false;
|
||||
|
||||
prefs().edit().putBoolean( PREF_MASK_PASSWORD, enabled ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isMaskPassword() {
|
||||
return prefs().getBoolean( PREF_MASK_PASSWORD, false );
|
||||
}
|
||||
|
||||
public boolean setFullName(@Nullable String value) {
|
||||
if (getFullName().equals( value ))
|
||||
return false;
|
||||
|
||||
prefs().edit().putString( PREF_FULL_NAME, value ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public String getFullName() {
|
||||
return prefs().getString( PREF_FULL_NAME, "" );
|
||||
}
|
||||
|
||||
public boolean setDefaultSiteType(@Nonnull MPSiteType value) {
|
||||
if (getDefaultSiteType().equals( value ))
|
||||
return false;
|
||||
|
||||
prefs().edit().putInt( PREF_SITE_TYPE, value.ordinal() ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPSiteType getDefaultSiteType() {
|
||||
return MPSiteType.values()[prefs().getInt( PREF_SITE_TYPE, MPSiteType.GeneratedLong.ordinal() )];
|
||||
}
|
||||
|
||||
public boolean setDefaultVersion(@Nonnull MasterKey.Version value) {
|
||||
if (getDefaultVersion().equals( value ))
|
||||
return false;
|
||||
|
||||
prefs().edit().putInt( PREF_ALGORITHM_VERSION, value.ordinal() ).apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MasterKey.Version getDefaultVersion() {
|
||||
return MasterKey.Version.values()[prefs().getInt( PREF_ALGORITHM_VERSION, MasterKey.Version.CURRENT.ordinal() )];
|
||||
}
|
||||
}
|
@@ -0,0 +1,34 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import android.content.res.Resources;
|
||||
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" );
|
||||
}
|
||||
}
|
@@ -0,0 +1,177 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import android.app.*;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.*;
|
||||
import android.view.View;
|
||||
import android.widget.*;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
import com.google.common.base.*;
|
||||
import com.google.common.collect.*;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.concurrent.*;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
public class TestActivity extends Activity implements MPTestSuite.Listener {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( TestActivity.class );
|
||||
|
||||
private final Preferences preferences = Preferences.get( this );
|
||||
private final ListeningExecutorService backgroundExecutor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
|
||||
private final ListeningExecutorService mainExecutor = MoreExecutors.listeningDecorator( new MainThreadExecutor() );
|
||||
|
||||
@BindView(R.id.progressView)
|
||||
ProgressBar progressView;
|
||||
|
||||
@BindView(R.id.statusView)
|
||||
TextView statusView;
|
||||
|
||||
@BindView(R.id.logView)
|
||||
TextView logView;
|
||||
|
||||
@BindView(R.id.actionButton)
|
||||
Button actionButton;
|
||||
|
||||
@BindView(R.id.nativeKDFField)
|
||||
CheckBox nativeKDFField;
|
||||
|
||||
private MPTestSuite testSuite;
|
||||
private ListenableFuture<Boolean> testFuture;
|
||||
private Runnable action;
|
||||
private ImmutableSet<String> testNames;
|
||||
|
||||
public static void startNoSkip(Context context) {
|
||||
context.startActivity( new Intent( context, TestActivity.class ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate( savedInstanceState );
|
||||
Res.init( getResources() );
|
||||
|
||||
setContentView( R.layout.activity_test );
|
||||
ButterKnife.bind( this );
|
||||
|
||||
nativeKDFField.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
|
||||
preferences.setNativeKDFEnabled( isChecked );
|
||||
MasterKey.setAllowNativeByDefault( isChecked );
|
||||
}
|
||||
} );
|
||||
|
||||
try {
|
||||
setStatus( 0, 0, null );
|
||||
testSuite = new MPTestSuite();
|
||||
testSuite.setListener( this );
|
||||
testNames = FluentIterable.from( testSuite.getTests().getCases() ).transform(
|
||||
new Function<MPTests.Case, String>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public String apply(@Nullable final MPTests.Case input) {
|
||||
return input == null? null: input.identifier;
|
||||
}
|
||||
} ).filter( Predicates.notNull() ).toSet();
|
||||
}
|
||||
catch (MPTestSuite.UnavailableException e) {
|
||||
logger.err( e, "While loading test suite" );
|
||||
setStatus( R.string.tests_unavailable, R.string.tests_btn_unavailable, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finish();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
nativeKDFField.setChecked( preferences.isAllowNativeKDF() );
|
||||
|
||||
if (testFuture == null)
|
||||
startTestSuite();
|
||||
}
|
||||
|
||||
private void startTestSuite() {
|
||||
if (testFuture != null)
|
||||
testFuture.cancel( true );
|
||||
|
||||
MasterKey.setAllowNativeByDefault( preferences.isAllowNativeKDF() );
|
||||
|
||||
setStatus( R.string.tests_testing, R.string.tests_btn_testing, null );
|
||||
Futures.addCallback( testFuture = backgroundExecutor.submit( testSuite ), new FutureCallback<Boolean>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final Boolean result) {
|
||||
if (result != null && result)
|
||||
setStatus( R.string.tests_passed, R.string.tests_btn_passed, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
preferences.setTestsPassed( testNames );
|
||||
finish();
|
||||
}
|
||||
} );
|
||||
else
|
||||
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
startTestSuite();
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
logger.err( t, "While running test suite" );
|
||||
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
finish();
|
||||
}
|
||||
} );
|
||||
}
|
||||
}, mainExecutor );
|
||||
}
|
||||
|
||||
public void onAction(View v) {
|
||||
if (action != null)
|
||||
action.run();
|
||||
}
|
||||
|
||||
private void setStatus(int statusId, int buttonId, @Nullable Runnable action) {
|
||||
this.action = action;
|
||||
|
||||
if (statusId == 0)
|
||||
statusView.setText( null );
|
||||
else
|
||||
statusView.setText( statusId );
|
||||
|
||||
if (buttonId == 0)
|
||||
actionButton.setText( null );
|
||||
else
|
||||
actionButton.setText( buttonId );
|
||||
actionButton.setEnabled( action != null );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void progress(final int current, final int max, final String messageFormat, final Object... args) {
|
||||
runOnUiThread( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
logView.append( strf( '\n' + messageFormat, args ) );
|
||||
|
||||
progressView.setMax( max );
|
||||
progressView.setProgress( current );
|
||||
}
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
BIN
platform-android/src/main/res/drawable-mdpi/background.png
Normal file
After Width: | Height: | Size: 82 KiB |
BIN
platform-android/src/main/res/drawable-mdpi/icon.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
platform-android/src/main/res/drawable-mdpi/icon_gears.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
platform-android/src/main/res/drawable-mdpi/icon_key.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
platform-android/src/main/res/drawable-mdpi/icon_plus.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
platform-android/src/main/res/drawable-mdpi/img_identity.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
platform-android/src/main/res/drawable-mdpi/img_key.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
platform-android/src/main/res/drawable-mdpi/img_stats.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
platform-android/src/main/res/drawable-xhdpi/background.png
Normal file
After Width: | Height: | Size: 292 KiB |
BIN
platform-android/src/main/res/drawable-xhdpi/icon.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
platform-android/src/main/res/drawable-xhdpi/icon_gears.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
platform-android/src/main/res/drawable-xhdpi/icon_key.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
platform-android/src/main/res/drawable-xhdpi/icon_plus.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
platform-android/src/main/res/drawable-xhdpi/img_identity.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
platform-android/src/main/res/drawable-xhdpi/img_key.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
platform-android/src/main/res/drawable-xhdpi/img_stats.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
platform-android/src/main/res/drawable-xxxhdpi/background.png
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
platform-android/src/main/res/drawable-xxxhdpi/icon.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
platform-android/src/main/res/drawable/divider.png
Normal file
After Width: | Height: | Size: 697 B |
273
platform-android/src/main/res/layout/activity_emergency.xml
Normal file
@@ -0,0 +1,273 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
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:padding="20dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/img_identity" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/fullNameField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nextFocusForward="@+id/masterPasswordField"
|
||||
android:inputType="text|textCapWords|textPersonName"
|
||||
android:hint="@string/fullName_hint"
|
||||
android:gravity="center"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/rememberFullNameField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nextFocusForward="@+id/rememberPasswordField"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/remember" />
|
||||
|
||||
<EditText
|
||||
android:id="@id/masterPasswordField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nextFocusForward="@+id/siteNameField"
|
||||
android:inputType="text|textPassword"
|
||||
android:hint="@string/masterPassword_hint"
|
||||
android:gravity="center"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@id/rememberPasswordField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/forgetOnClose" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/img_key" />
|
||||
|
||||
<EditText
|
||||
android:id="@id/siteNameField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nextFocusForward="@+id/sitePasswordField"
|
||||
android:inputType="text|textNoSuggestions|textUri"
|
||||
android:hint="@string/siteName_hint"
|
||||
android:gravity="center"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="invisible"
|
||||
android:indeterminate="true"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<Button
|
||||
android:id="@id/sitePasswordField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nextFocusForward="@+id/siteTypeButton"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="28sp"
|
||||
tools:text="LuxdZozvDuma4["
|
||||
android:onClick="copySitePassword" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sitePasswordTip"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/sitePasswordField"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/sitePassword_hint" />
|
||||
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/maskPasswordField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/maskPassword" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/divider" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="?android:buttonBarStyle"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@id/siteTypeButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:nextFocusForward="@+id/counterField"
|
||||
android:gravity="center"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:drawableStart="@drawable/icon_key"
|
||||
android:drawablePadding="8dp"
|
||||
android:background="@android:color/transparent"
|
||||
tools:text="Long" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/siteTypeButton"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/siteType_hint" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@id/counterField"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:nextFocusForward="@+id/siteVersionButton"
|
||||
android:gravity="center"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:drawableStart="@drawable/icon_plus"
|
||||
android:drawablePadding="8dp"
|
||||
android:background="@android:color/transparent"
|
||||
tools:text="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/siteVersionButton"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/siteCounter_hint" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<Button
|
||||
android:id="@id/siteVersionButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:nextFocusForward="@+id/rememberFullNameField"
|
||||
android:gravity="center"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="16sp"
|
||||
android:drawableStart="@drawable/icon_gears"
|
||||
android:drawablePadding="8dp"
|
||||
android:background="@android:color/transparent"
|
||||
tools:text="3" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/siteVersionButton"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/siteVersion_hint" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="16sp"
|
||||
android:text="@string/btn_tests"
|
||||
android:onClick="integrityTests"
|
||||
android:background="@android:color/transparent" />
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
77
platform-android/src/main/res/layout/activity_test.xml
Normal file
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
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:padding="20dp"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<View
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
android:src="@drawable/img_stats" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="8dp"
|
||||
tools:max="100"
|
||||
tools:progress="80"
|
||||
style="?android:progressBarStyleHorizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/statusView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:labelFor="@id/sitePasswordField"
|
||||
android:gravity="center"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/tests_testing" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/logView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginTop="20dp"
|
||||
android:gravity="bottom"
|
||||
android:background="@android:color/transparent"
|
||||
android:textSize="9sp"
|
||||
android:textColor="@android:color/tertiary_text_dark" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/actionButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:enabled="false"
|
||||
android:text="@string/tests_btn_testing"
|
||||
android:onClick="onAction" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/nativeKDFField"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:textColor="@android:color/tertiary_text_dark"
|
||||
android:text="@string/nativeKDF" />
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
5
platform-android/src/main/res/layout/spinner_item.xml
Normal file
@@ -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" />
|
25
platform-android/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Master Password</string>
|
||||
<string name="remember">Remember</string>
|
||||
<string name="forgetOnClose">Forget on close</string>
|
||||
<string name="maskPassword">Hide password</string>
|
||||
<string name="fullName_hint">Your full name</string>
|
||||
<string name="masterPassword_hint">Your master password</string>
|
||||
<string name="siteName_hint">eg. google.com</string>
|
||||
<string name="sitePassword_hint">Tap to copy</string>
|
||||
<string name="siteType_hint">Type</string>
|
||||
<string name="siteCounter_hint">Counter</string>
|
||||
<string name="siteVersion_hint">Algorithm</string>
|
||||
<string name="empty" />
|
||||
<string name="btn_tests">Integrity Tests ❯</string>
|
||||
<string name="tests_unavailable">Test suite unavailable.</string>
|
||||
<string name="tests_btn_unavailable">Retest</string>
|
||||
<string name="tests_testing">Testing device\'s password generation integrity…</string>
|
||||
<string name="tests_btn_testing">Please Stand By…</string>
|
||||
<string name="tests_failed">Incompatible device or OS.</string>
|
||||
<string name="tests_btn_failed">Retest</string>
|
||||
<string name="tests_passed">Integrity checks passed!</string>
|
||||
<string name="tests_btn_passed">Close</string>
|
||||
<string name="nativeKDF">Use native key derivation</string>
|
||||
</resources>
|
6
platform-android/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="MPTheme" parent="android:Theme.Holo.Dialog.MinWidth">
|
||||
</style>
|
||||
</resources>
|