Merge branch 'upstream' into webview

Conflicts:
	app/build.gradle
This commit is contained in:
Matthew Wong
2015-12-31 06:24:05 -05:00
11 changed files with 263 additions and 94 deletions

View File

@@ -4,13 +4,13 @@ apply plugin: 'eclipse'
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
buildToolsVersion '23.0.2'
defaultConfig {
applicationId "com.zeapo.pwdstore"
minSdkVersion 15
targetSdkVersion 23
versionCode 55
versionName "1.2.0.35"
versionCode 58
versionName "1.2.0.38"
}
compileOptions {
@@ -25,6 +25,8 @@ android {
packagingOptions {
exclude '.readme'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
//
@@ -61,13 +63,13 @@ dependencies {
compile 'com.android.support:cardview-v7:23.1.1'
compile 'com.android.support:design:23.1.1'
compile 'org.sufficientlysecure:openpgp-api:9.0'
compile 'com.nononsenseapps:filepicker:2.4.2'
compile ('org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r') {
exclude group: 'org.apache.httpcomponents', module: 'httpclient'
}
compile 'com.jcraft:jsch:0.1.53'
compile 'org.apache.commons:commons-io:1.3.2'
compile 'com.jayway.android.robotium:robotium-solo:5.3.1'
compile 'net.rdrei.android.dirchooser:library:2.1@aar'
compile group: 'com.google.guava', name: 'guava', version: '18.0'
}
tasks.findAll { // make all tasks whose name starts with 'assemble'...

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.zeapo.pwdstore">
package="com.zeapo.pwdstore">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
@@ -70,7 +70,15 @@
android:value="com.zeapo.pwdstore.PasswordStore" />
</activity>
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
<activity
android:name="com.nononsenseapps.filepicker.FilePickerActivity"
android:label="@string/app_name"
android:theme="@style/FilePickerTheme">
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -1,13 +1,19 @@
package com.zeapo.pwdstore;
import android.Manifest;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
@@ -16,6 +22,7 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import com.zeapo.pwdstore.crypto.PgpHandler;
import com.zeapo.pwdstore.git.GitActivity;
@@ -47,19 +54,76 @@ public class PasswordStore extends AppCompatActivity {
private final static int NEW_REPO_BUTTON = 402;
private final static int HOME = 403;
private final static int REQUEST_EXTERNAL_STORAGE = 50;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pwdstore);
settings = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
activity = this;
PRNGFixes.apply();
// If user opens app with permission granted then revokes and returns,
// prevent attempt to create password list fragment
if (savedInstanceState != null && (!settings.getBoolean("git_external", false)
|| ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) {
savedInstanceState = null;
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pwdstore);
}
@Override
public void onResume(){
super.onResume();
checkLocalRepository();
// do not attempt to checkLocalRepository() if no storage permission: immediate crash
if (settings.getBoolean("git_external", false)) {
if (ContextCompat.checkSelfPermission(activity,
Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
Manifest.permission.READ_EXTERNAL_STORAGE)) {
Snackbar snack = Snackbar.make(findViewById(R.id.main_layout), "The store is on the sdcard but the app does not have permission to access it. Please give permission.",
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.dialog_ok, new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_EXTERNAL_STORAGE);
}
});
snack.show();
View view = snack.getView();
TextView tv = (TextView) view.findViewById(android.support.design.R.id.snackbar_text);
tv.setTextColor(Color.WHITE);
tv.setMaxLines(10);
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_EXTERNAL_STORAGE);
}
} else {
checkLocalRepository();
}
} else {
checkLocalRepository();
}
}
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case REQUEST_EXTERNAL_STORAGE: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
checkLocalRepository();
}
}
}
}
@Override
@@ -495,33 +559,9 @@ public class PasswordStore extends AppCompatActivity {
PasswordRepository.closeRepository();
new AlertDialog.Builder(this)
.setTitle("Repositiory location")
.setTitle("Repository location")
.setMessage("Select where to create or clone your password repository.")
.setPositiveButton("External", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
settings.edit().putBoolean("git_external", true).apply();
if (settings.getString("git_external_repo", null) == null) {
Intent intent = new Intent(activity, UserPreference.class);
intent.putExtra("operation", "git_external");
startActivityForResult(intent, operation);
} else {
switch (operation) {
case NEW_REPO_BUTTON:
initializeRepositoryInfo();
break;
case CLONE_REPO_BUTTON:
PasswordRepository.initialize(PasswordStore.this);
Intent intent = new Intent(activity, GitActivity.class);
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
break;
}
}
}
})
.setNegativeButton("Internal", new DialogInterface.OnClickListener() {
.setPositiveButton("Hidden (preferred)", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
settings.edit().putBoolean("git_external", false).apply();
@@ -539,6 +579,46 @@ public class PasswordStore extends AppCompatActivity {
}
}
})
.setNegativeButton("SD-Card", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
settings.edit().putBoolean("git_external", true).apply();
if (settings.getString("git_external_repo", null) == null) {
Intent intent = new Intent(activity, UserPreference.class);
intent.putExtra("operation", "git_external");
startActivityForResult(intent, operation);
} else {
new AlertDialog.Builder(activity).
setTitle("Directory already selected").
setMessage("Do you want to use \"" + settings.getString("git_external_repo", null) + "\"?").
setPositiveButton("Use", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (operation) {
case NEW_REPO_BUTTON:
initializeRepositoryInfo();
break;
case CLONE_REPO_BUTTON:
PasswordRepository.initialize(PasswordStore.this);
Intent intent = new Intent(activity, GitActivity.class);
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
break;
}
}
}).
setNegativeButton("Change", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(activity, UserPreference.class);
intent.putExtra("operation", "git_external");
startActivityForResult(intent, operation);
}
}).show();
}
}
})
.show();
}

View File

@@ -1,6 +1,7 @@
package com.zeapo.pwdstore;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
@@ -8,6 +9,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
@@ -15,7 +17,6 @@ import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.SpannableStringBuilder;
import android.view.MenuItem;
import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
@@ -23,13 +24,12 @@ import android.widget.Toast;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.nononsenseapps.filepicker.FilePickerActivity;
import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity;
import com.zeapo.pwdstore.crypto.PgpHandler;
import com.zeapo.pwdstore.git.GitActivity;
import com.zeapo.pwdstore.utils.PasswordRepository;
import net.rdrei.android.dirchooser.DirectoryChooserActivity;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.openintents.openpgp.util.OpenPgpKeyPreference;
@@ -124,7 +124,8 @@ public class UserPreference extends AppCompatActivity {
public boolean onPreferenceClick(Preference preference) {
new AlertDialog.Builder(callingActivity).
setTitle(R.string.pref_dialog_delete_title).
setMessage(R.string.pref_dialog_delete_msg).
setMessage(getResources().getString(R.string.dialog_delete_msg)
+ " \n" + PasswordRepository.getWorkTree().toString()).
setCancelable(false).
setPositiveButton(R.string.dialog_delete, new DialogInterface.OnClickListener() {
@Override
@@ -230,6 +231,7 @@ public class UserPreference extends AppCompatActivity {
public void onStart() {
super.onStart();
final SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences();
findPreference("pref_select_external").setSummary(getPreferenceManager().getSharedPreferences().getString("git_external_repo", "No external repository selected"));
findPreference("ssh_see_key").setEnabled(sharedPreferences.getBoolean("use_generated_key", false));
// see if the autofill service is enabled and check the preference accordingly
@@ -264,11 +266,32 @@ public class UserPreference extends AppCompatActivity {
}
public void selectExternalGitRepository() {
Intent intent = new Intent(this, DirectoryChooserActivity.class);
intent.putExtra(DirectoryChooserActivity.EXTRA_NEW_DIR_NAME,
"passwordstore");
final Activity activity = this;
new AlertDialog.Builder(this).
setTitle("Choose where to store the passwords").
setMessage("You must select a directory where to store your passwords. If you want " +
"to store your passwords within the hidden storage of the application, " +
"cancel this dialog and disable the \"External Repository\" option.").
setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// This always works
Intent i = new Intent(activity.getApplicationContext(), FilePickerActivity.class);
// This works if you defined the intent filter
// Intent i = new Intent(Intent.ACTION_GET_CONTENT);
// Set these depending on your use case. These are the defaults.
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().getPath());
startActivityForResult(i, SELECT_GIT_DIRECTORY);
}
}).
setNegativeButton(R.string.dialog_cancel, null).show();
startActivityForResult(intent, SELECT_GIT_DIRECTORY);
}
@Override
@@ -344,6 +367,11 @@ public class UserPreference extends AppCompatActivity {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("use_generated_key", false);
editor.apply();
//delete the public key from generation
File file = new File(getFilesDir() + "/.ssh_key.pub");
file.delete();
setResult(RESULT_OK);
finish();
} catch (IOException e) {
@@ -371,17 +399,53 @@ public class UserPreference extends AppCompatActivity {
}
}
break;
case SELECT_GIT_DIRECTORY: {
final Uri uri = data.getData();
if (uri.getPath().equals(Environment.getExternalStorageDirectory().getPath())) {
// the user wants to use the root of the sdcard as a store...
new AlertDialog.Builder(this).
setTitle("SD-Card root selected").
setMessage("You have selected the root of your sdcard for the store. " +
"This is extremely dangerous and you will lose your data " +
"as its content will be deleted").
setPositiveButton("Remove everything", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
.edit()
.putString("git_external_repo", uri.getPath())
.apply();
}
}).
setNegativeButton(R.string.dialog_cancel, null).show();
} else if (new File(uri.getPath()).listFiles().length != 0) {
new AlertDialog.Builder(this).
setTitle("Directory not empty").
setMessage("You have selected a non-empty directory for the store. " +
"This is extremely dangerous and you will lose your data " +
"as its content will be deleted").
setPositiveButton("Remove everything", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
.edit()
.putString("git_external_repo", uri.getPath())
.apply();
}
}).
setNegativeButton(R.string.dialog_cancel, null).show();
} else {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
.edit()
.putString("git_external_repo", uri.getPath())
.apply();
}
}
break;
default:
break;
}
}
// why do they have to use a different resultCode than OK :/
if (requestCode == SELECT_GIT_DIRECTORY && resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
.edit()
.putString("git_external_repo", data.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR))
.apply();
}
}
}

View File

@@ -560,6 +560,7 @@ public class PgpHandler extends AppCompatActivity implements OpenPgpServiceConne
}
// TODO (low priority but still...) android M potential permissions crashes
@Override
public void onBound(IOpenPgpService2 service) {
Log.i("PGP", "ISBOUND!!");

View File

@@ -45,7 +45,6 @@ public class GitActivity extends AppCompatActivity {
private File localDir;
private String hostname;
private String username;
private String port;
private SharedPreferences settings;
@@ -404,7 +403,7 @@ public class GitActivity extends AppCompatActivity {
/**
* Saves the configuration found in the form
*/
private void saveConfiguration() {
private boolean saveConfiguration() {
// remember the settings
SharedPreferences.Editor editor = settings.edit();
@@ -416,11 +415,39 @@ public class GitActivity extends AppCompatActivity {
editor.putString("git_remote_port", ((EditText) findViewById(R.id.server_port)).getText().toString());
editor.putString("git_remote_uri", ((EditText) findViewById(R.id.clone_uri)).getText().toString());
// 'save' hostname variable for use by addRemote() either here or later
// in syncRepository()
hostname = ((EditText) findViewById(R.id.clone_uri)).getText().toString();
port = ((EditText) findViewById(R.id.server_port)).getText().toString();
// don't ask the user, take off the protocol that he puts in
hostname = hostname.replaceFirst("^.+://", "");
((TextView) findViewById(R.id.clone_uri)).setText(hostname);
if (!protocol.equals("ssh://")) {
hostname = protocol + hostname;
} else {
// if the port is explicitly given, jgit requires the ssh://
if (!port.isEmpty())
hostname = protocol + hostname;
// did he forget the username?
if (!hostname.matches("^.+@.+")) {
new AlertDialog.Builder(this).
setMessage(activity.getResources().getString(R.string.forget_username_dialog_text)).
setPositiveButton(activity.getResources().getString(R.string.dialog_oops), null).
show();
return false;
}
}
if (PasswordRepository.isInitialized()) {
PasswordRepository.addRemote("origin", ((EditText) findViewById(R.id.clone_uri)).getText().toString(), true);
// don't just use the clone_uri text, need to use hostname which has
// had the proper protocol prepended
PasswordRepository.addRemote("origin", hostname, true);
}
editor.apply();
return true;
}
/**
@@ -429,7 +456,8 @@ public class GitActivity extends AppCompatActivity {
* @param view
*/
public void saveConfiguration(View view) {
saveConfiguration();
if (!saveConfiguration())
return;
finish();
}
@@ -443,45 +471,14 @@ public class GitActivity extends AppCompatActivity {
PasswordRepository.initialize(this);
}
localDir = PasswordRepository.getWorkTree();
hostname = ((EditText) findViewById(R.id.clone_uri)).getText().toString();
port = ((EditText) findViewById(R.id.server_port)).getText().toString();
// don't ask the user, take off the protocol that he puts in
hostname = hostname.replaceFirst("^.+://", "");
((TextView) findViewById(R.id.clone_uri)).setText(hostname);
// now cheat a little and prepend the real protocol
// jGit does not accept a ssh:// but requires https://
if (!protocol.equals("ssh://")) {
hostname = protocol + hostname;
} else {
// if the port is explicitly given, jgit requires the ssh://
if (!port.isEmpty())
hostname = protocol + hostname;
// did he forget the username?
if (!hostname.matches("^.+@.+")) {
new AlertDialog.Builder(this).
setMessage(activity.getResources().getString(R.string.forget_username_dialog_text)).
setPositiveButton(activity.getResources().getString(R.string.dialog_oops), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
}).
show();
return;
}
username = hostname.split("@")[0];
}
saveConfiguration();
if (!saveConfiguration())
return;
if (localDir.exists() && localDir.listFiles().length != 0) {
new AlertDialog.Builder(this).
setTitle(R.string.dialog_delete_title).
setMessage(R.string.dialog_delete_msg).
setMessage(getResources().getString(R.string.dialog_delete_msg) + " " + localDir.toString()).
setCancelable(false).
setPositiveButton(R.string.dialog_delete,
new DialogInterface.OnClickListener() {

View File

@@ -9,7 +9,7 @@
<string name="action_settings">Nastavení</string>
<string name="hello_world">Hello world!</string>
<string name="dialog_delete_title">Adresář již existuje</string>
<string name="dialog_delete_msg">Cílový adresář již existuje. Aktuální verze podporuje pouze jedno úložiště. Opravdu smazat aktuální adresář úložiště hesel?</string>
<string name="dialog_delete_msg">Cílový adresář již existuje. Aktuální verze podporuje pouze jedno úložiště. Opravdu smazat aktuální adresář úložiště hesel:</string>
<string name="dialog_delete">Smazat adresář</string>
<string name="dialog_do_not_delete">Zrušit</string>
<string name="title_activity_git_clone">Informace repozitáře</string>

View File

@@ -9,7 +9,7 @@
<string name="action_settings">Settings</string>
<string name="hello_world">Hello world!</string>
<string name="dialog_delete_title">Directory already exist</string>
<string name="dialog_delete_msg">Target directory already exist. Current version support only a single store. Do you want to delete the current password store directory?</string>
<string name="dialog_delete_msg">Target directory already exist. Current version support only a single store. Do you want to delete the current password store directory:</string>
<string name="dialog_delete">Delete directory</string>
<string name="dialog_do_not_delete">Cancel</string>
<string name="title_activity_git_clone">Repository information</string>

View File

@@ -13,4 +13,21 @@
<style name="ActionMode" parent="@style/Widget.AppCompat.ActionMode">
<item name="background">@color/blue_grey_700</item>
</style>
<!-- You can also inherit from NNF_BaseTheme.Light -->
<style name="FilePickerTheme" parent="NNF_BaseTheme">
<!-- Set these to match your theme -->
<item name="colorPrimary">@color/blue_grey_500</item>
<item name="colorPrimaryDark">@color/blue_grey_700</item>
<item name="colorAccent">@color/teal_A700</item>
<!-- Need to set this also to style create folder dialog -->
<item name="alertDialogTheme">@style/FilePickerAlertDialogTheme</item>
</style>
<style name="FilePickerAlertDialogTheme" parent="Theme.AppCompat.Dialog.Alert">
<item name="colorPrimary">@color/blue_grey_500</item>
<item name="colorPrimaryDark">@color/blue_grey_700</item>
<item name="colorAccent">@color/teal_A700</item>
</style>
</resources>

View File

@@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.0'
classpath 'com.android.tools.build:gradle:1.5.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@@ -1,6 +1,6 @@
#Thu Dec 04 19:46:18 CET 2014
#Wed Dec 23 00:45:56 EST 2015
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip