mirror of
https://github.com/android-password-store/Android-Password-Store
synced 2025-08-30 13:57:47 +00:00
Bump minSdk to 21 (#466)
* Bump minSdk to 21 Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com> * PasswordGenerator: Constify things Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com> * Deprecate PRNG fixes The problem being fixed doesn't exist on SDK 21 and above. Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com> * treewide: Switch to lambdas Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com> * treewide: Formatting fixes Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com> * treewide: Remove useless casts and add missing annotations Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com>
This commit is contained in:
@@ -7,7 +7,7 @@ android {
|
|||||||
compileSdkVersion 28
|
compileSdkVersion 28
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.zeapo.pwdstore"
|
applicationId "com.zeapo.pwdstore"
|
||||||
minSdkVersion 16
|
minSdkVersion 21
|
||||||
targetSdkVersion 28
|
targetSdkVersion 28
|
||||||
versionCode 10302
|
versionCode 10302
|
||||||
versionName "1.3.2"
|
versionName "1.3.2"
|
||||||
@@ -15,8 +15,8 @@ android {
|
|||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_7
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_7
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
lintOptions {
|
lintOptions {
|
||||||
abortOnError true // make sure build fails with lint errors!
|
abortOnError true // make sure build fails with lint errors!
|
||||||
@@ -79,8 +79,6 @@ dependencies {
|
|||||||
androidTestImplementation 'androidx.test:rules:1.1.0-alpha4'
|
androidTestImplementation 'androidx.test:rules:1.1.0-alpha4'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0-alpha4'
|
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0-alpha4'
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@@ -4,9 +4,10 @@ import android.content.Context;
|
|||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.View;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
class DividerItemDecoration extends RecyclerView.ItemDecoration {
|
class DividerItemDecoration extends RecyclerView.ItemDecoration {
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ class DividerItemDecoration extends RecyclerView.ItemDecoration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
|
||||||
int left = parent.getPaddingLeft();
|
int left = parent.getPaddingLeft();
|
||||||
int right = parent.getWidth() - parent.getPaddingRight();
|
int right = parent.getWidth() - parent.getPaddingRight();
|
||||||
|
|
||||||
|
@@ -11,14 +11,13 @@ import java.io.UnsupportedEncodingException;
|
|||||||
public class PasswordEntry {
|
public class PasswordEntry {
|
||||||
|
|
||||||
private static final String[] USERNAME_FIELDS = new String[]{"login", "username"};
|
private static final String[] USERNAME_FIELDS = new String[]{"login", "username"};
|
||||||
|
|
||||||
private String extraContent;
|
|
||||||
private final String password;
|
private final String password;
|
||||||
private final String username;
|
private final String username;
|
||||||
private final String totpSecret;
|
private final String totpSecret;
|
||||||
private final String hotpSecret;
|
private final String hotpSecret;
|
||||||
private final Long hotpCounter;
|
private final Long hotpCounter;
|
||||||
private final String content;
|
private final String content;
|
||||||
|
private String extraContent;
|
||||||
private boolean isIncremented = false;
|
private boolean isIncremented = false;
|
||||||
|
|
||||||
public PasswordEntry(final ByteArrayOutputStream os) throws UnsupportedEncodingException {
|
public PasswordEntry(final ByteArrayOutputStream os) throws UnsupportedEncodingException {
|
||||||
@@ -34,7 +33,7 @@ public class PasswordEntry {
|
|||||||
hotpCounter = findHotpCounter(content);
|
hotpCounter = findHotpCounter(content);
|
||||||
extraContent = findExtraContent(passContent);
|
extraContent = findExtraContent(passContent);
|
||||||
username = findUsername();
|
username = findUsername();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPassword() {
|
public String getPassword() {
|
||||||
return password;
|
return password;
|
||||||
@@ -76,7 +75,9 @@ public class PasswordEntry {
|
|||||||
return hotpSecret != null && hotpCounter != null;
|
return hotpSecret != null && hotpCounter != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hotpIsIncremented() { return isIncremented; }
|
public boolean hotpIsIncremented() {
|
||||||
|
return isIncremented;
|
||||||
|
}
|
||||||
|
|
||||||
public void incrementHotp() {
|
public void incrementHotp() {
|
||||||
for (String line : content.split("\n")) {
|
for (String line : content.split("\n")) {
|
||||||
@@ -126,7 +127,7 @@ public class PasswordEntry {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String findExtraContent(String [] passContent) {
|
private String findExtraContent(String[] passContent) {
|
||||||
String extraContent = passContent.length > 1 ? passContent[1] : "";
|
String extraContent = passContent.length > 1 ? passContent[1] : "";
|
||||||
// if there is a HOTP URI, we must return the extra content with the counter incremented
|
// if there is a HOTP URI, we must return the extra content with the counter incremented
|
||||||
if (hasHotp()) {
|
if (hasHotp()) {
|
||||||
|
@@ -4,16 +4,16 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.zeapo.pwdstore.utils.PasswordItem;
|
import com.zeapo.pwdstore.utils.PasswordItem;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRecyclerAdapter;
|
import com.zeapo.pwdstore.utils.PasswordRecyclerAdapter;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||||
@@ -24,16 +24,12 @@ import java.util.Stack;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment representing a list of Items.
|
* A fragment representing a list of Items.
|
||||||
* <p />
|
* <p/>
|
||||||
* Large screen devices (such as tablets) are supported by replacing the ListView
|
* Large screen devices (such as tablets) are supported by replacing the ListView
|
||||||
* with a GridView.
|
* with a GridView.
|
||||||
* <p />
|
* <p/>
|
||||||
*/
|
*/
|
||||||
public class PasswordFragment extends Fragment{
|
public class PasswordFragment extends Fragment {
|
||||||
|
|
||||||
public interface OnFragmentInteractionListener {
|
|
||||||
void onFragmentInteraction(PasswordItem item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// store the pass files list in a stack
|
// store the pass files list in a stack
|
||||||
private Stack<ArrayList<PasswordItem>> passListStack;
|
private Stack<ArrayList<PasswordItem>> passListStack;
|
||||||
@@ -43,12 +39,12 @@ public class PasswordFragment extends Fragment{
|
|||||||
private RecyclerView recyclerView;
|
private RecyclerView recyclerView;
|
||||||
private OnFragmentInteractionListener mListener;
|
private OnFragmentInteractionListener mListener;
|
||||||
private SharedPreferences settings;
|
private SharedPreferences settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||||
* fragment (e.g. upon screen orientation changes).
|
* fragment (e.g. upon screen orientation changes).
|
||||||
*/
|
*/
|
||||||
public PasswordFragment() { }
|
public PasswordFragment() {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -60,18 +56,18 @@ public class PasswordFragment extends Fragment{
|
|||||||
scrollPosition = new Stack<>();
|
scrollPosition = new Stack<>();
|
||||||
pathStack = new Stack<>();
|
pathStack = new Stack<>();
|
||||||
recyclerAdapter = new PasswordRecyclerAdapter((PasswordStore) getActivity(), mListener,
|
recyclerAdapter = new PasswordRecyclerAdapter((PasswordStore) getActivity(), mListener,
|
||||||
PasswordRepository.getPasswords(new File(path), PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()));
|
PasswordRepository.getPasswords(new File(path), PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.password_recycler_view, container, false);
|
View view = inflater.inflate(R.layout.password_recycler_view, container, false);
|
||||||
|
|
||||||
// use a linear layout manager
|
// use a linear layout manager
|
||||||
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
|
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
|
||||||
|
|
||||||
recyclerView = (RecyclerView) view.findViewById(R.id.pass_recycler);
|
recyclerView = view.findViewById(R.id.pass_recycler);
|
||||||
recyclerView.setLayoutManager(mLayoutManager);
|
recyclerView.setLayoutManager(mLayoutManager);
|
||||||
|
|
||||||
// use divider
|
// use divider
|
||||||
@@ -80,13 +76,8 @@ public class PasswordFragment extends Fragment{
|
|||||||
// Set the adapter
|
// Set the adapter
|
||||||
recyclerView.setAdapter(recyclerAdapter);
|
recyclerView.setAdapter(recyclerAdapter);
|
||||||
|
|
||||||
final FloatingActionButton fab = (FloatingActionButton) view.findViewById(R.id.fab);
|
final FloatingActionButton fab = view.findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(new View.OnClickListener() {
|
fab.setOnClickListener(v -> ((PasswordStore) getActivity()).createPassword());
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
((PasswordStore) getActivity()).createPassword();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
registerForContextMenu(recyclerView);
|
registerForContextMenu(recyclerView);
|
||||||
return view;
|
return view;
|
||||||
@@ -96,34 +87,32 @@ public class PasswordFragment extends Fragment{
|
|||||||
public void onAttach(final Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
try {
|
try {
|
||||||
mListener = new OnFragmentInteractionListener() {
|
mListener = item -> {
|
||||||
public void onFragmentInteraction(PasswordItem item) {
|
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
|
||||||
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
|
// push the current password list (non filtered plz!)
|
||||||
// push the current password list (non filtered plz!)
|
passListStack.push(pathStack.isEmpty() ?
|
||||||
passListStack.push(pathStack.isEmpty() ?
|
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(context), getSortOrder()) :
|
||||||
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(context), getSortOrder()) :
|
PasswordRepository.getPasswords(pathStack.peek(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
|
||||||
PasswordRepository.getPasswords(pathStack.peek(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
|
//push the category were we're going
|
||||||
//push the category were we're going
|
pathStack.push(item.getFile());
|
||||||
pathStack.push(item.getFile());
|
scrollPosition.push(recyclerView.getVerticalScrollbarPosition());
|
||||||
scrollPosition.push(recyclerView.getVerticalScrollbarPosition());
|
|
||||||
|
|
||||||
recyclerView.scrollToPosition(0);
|
recyclerView.scrollToPosition(0);
|
||||||
recyclerAdapter.clear();
|
recyclerAdapter.clear();
|
||||||
recyclerAdapter.addAll(PasswordRepository.getPasswords(item.getFile(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
|
recyclerAdapter.addAll(PasswordRepository.getPasswords(item.getFile(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
|
||||||
|
|
||||||
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
} else {
|
||||||
|
if (getArguments().getBoolean("matchWith", false)) {
|
||||||
|
((PasswordStore) getActivity()).matchPasswordWithApp(item);
|
||||||
} else {
|
} else {
|
||||||
if (getArguments().getBoolean("matchWith", false)) {
|
((PasswordStore) getActivity()).decryptPassword(item);
|
||||||
((PasswordStore) getActivity()).matchPasswordWithApp(item);
|
|
||||||
} else {
|
|
||||||
((PasswordStore) getActivity()).decryptPassword(item);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
throw new ClassCastException(context.toString()
|
throw new ClassCastException(context.toString()
|
||||||
+ " must implement OnFragmentInteractionListener");
|
+ " must implement OnFragmentInteractionListener");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,12 +135,13 @@ public class PasswordFragment extends Fragment{
|
|||||||
public void refreshAdapter() {
|
public void refreshAdapter() {
|
||||||
recyclerAdapter.clear();
|
recyclerAdapter.clear();
|
||||||
recyclerAdapter.addAll(pathStack.isEmpty() ?
|
recyclerAdapter.addAll(pathStack.isEmpty() ?
|
||||||
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()) :
|
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()) :
|
||||||
PasswordRepository.getPasswords(pathStack.peek(), PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()));
|
PasswordRepository.getPasswords(pathStack.peek(), PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* filters the list adapter
|
* filters the list adapter
|
||||||
|
*
|
||||||
* @param filter the filter to apply
|
* @param filter the filter to apply
|
||||||
*/
|
*/
|
||||||
public void filterAdapter(String filter) {
|
public void filterAdapter(String filter) {
|
||||||
@@ -166,8 +156,9 @@ public class PasswordFragment extends Fragment{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* recursively filters a directory and extract all the matching items
|
* recursively filters a directory and extract all the matching items
|
||||||
|
*
|
||||||
* @param filter the filter to apply
|
* @param filter the filter to apply
|
||||||
* @param dir the directory to filter
|
* @param dir the directory to filter
|
||||||
*/
|
*/
|
||||||
private void recursiveFilter(String filter, File dir) {
|
private void recursiveFilter(String filter, File dir) {
|
||||||
// on the root the pathStack is empty
|
// on the root the pathStack is empty
|
||||||
@@ -205,6 +196,7 @@ public class PasswordFragment extends Fragment{
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* gets the current directory
|
* gets the current directory
|
||||||
|
*
|
||||||
* @return the current directory
|
* @return the current directory
|
||||||
*/
|
*/
|
||||||
public File getCurrentDir() {
|
public File getCurrentDir() {
|
||||||
@@ -227,4 +219,8 @@ public class PasswordFragment extends Fragment{
|
|||||||
private PasswordRepository.PasswordSortOrder getSortOrder() {
|
private PasswordRepository.PasswordSortOrder getSortOrder() {
|
||||||
return PasswordRepository.PasswordSortOrder.getSortOrder(settings);
|
return PasswordRepository.PasswordSortOrder.getSortOrder(settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface OnFragmentInteractionListener {
|
||||||
|
void onFragmentInteraction(PasswordItem item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -30,7 +29,6 @@ public class PasswordGeneratorDialogFragment extends DialogFragment {
|
|||||||
public PasswordGeneratorDialogFragment() {
|
public PasswordGeneratorDialogFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
@Override
|
@Override
|
||||||
@@ -64,49 +62,35 @@ public class PasswordGeneratorDialogFragment extends DialogFragment {
|
|||||||
TextView textView = view.findViewById(R.id.lengthNumber);
|
TextView textView = view.findViewById(R.id.lengthNumber);
|
||||||
textView.setText(Integer.toString(prefs.getInt("length", 20)));
|
textView.setText(Integer.toString(prefs.getInt("length", 20)));
|
||||||
|
|
||||||
((TextView) view.findViewById(R.id.passwordText)).setTypeface(monoTypeface);
|
TextView passwordText = view.findViewById(R.id.passwordText);
|
||||||
|
passwordText.setTypeface(monoTypeface);
|
||||||
|
|
||||||
builder.setPositiveButton(getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
|
builder.setPositiveButton(getResources().getString(R.string.dialog_ok), (dialog, which) -> {
|
||||||
@Override
|
EditText edit = callingActivity.findViewById(R.id.crypto_password_edit);
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
edit.setText(passwordText.getText());
|
||||||
EditText edit = callingActivity.findViewById(R.id.crypto_password_edit);
|
|
||||||
TextView generate = view.findViewById(R.id.passwordText);
|
|
||||||
edit.setText(generate.getText());
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.setNegativeButton(getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
|
builder.setNegativeButton(getResources().getString(R.string.dialog_cancel), (dialog, which) -> {
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.setNeutralButton(getResources().getString(R.string.pwgen_generate), null);
|
builder.setNeutralButton(getResources().getString(R.string.pwgen_generate), null);
|
||||||
|
|
||||||
final AlertDialog ad = builder.setTitle(this.getResources().getString(R.string.pwgen_title)).create();
|
final AlertDialog ad = builder.setTitle(this.getResources().getString(R.string.pwgen_title)).create();
|
||||||
ad.setOnShowListener(new DialogInterface.OnShowListener() {
|
ad.setOnShowListener(dialog -> {
|
||||||
@Override
|
setPreferences();
|
||||||
public void onShow(DialogInterface dialog) {
|
passwordText.setText(PasswordGenerator.INSTANCE.generate(getActivity().getApplicationContext()).get(0));
|
||||||
setPreferences();
|
|
||||||
TextView textView = view.findViewById(R.id.passwordText);
|
|
||||||
textView.setText(PasswordGenerator.INSTANCE.generate(getActivity().getApplicationContext()).get(0));
|
|
||||||
|
|
||||||
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
|
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
|
||||||
b.setOnClickListener(new View.OnClickListener() {
|
b.setOnClickListener(v -> {
|
||||||
@Override
|
setPreferences();
|
||||||
public void onClick(View v) {
|
passwordText.setText(PasswordGenerator.INSTANCE.generate(callingActivity.getApplicationContext()).get(0));
|
||||||
setPreferences();
|
});
|
||||||
TextView textView = view.findViewById(R.id.passwordText);
|
|
||||||
textView.setText(PasswordGenerator.INSTANCE.generate(callingActivity.getApplicationContext()).get(0));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return ad;
|
return ad;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPreferences () {
|
private void setPreferences() {
|
||||||
ArrayList<String> preferences = new ArrayList<>();
|
ArrayList<String> preferences = new ArrayList<>();
|
||||||
if (!((CheckBox) getDialog().findViewById(R.id.numerals)).isChecked()) {
|
if (!((CheckBox) getDialog().findViewById(R.id.numerals)).isChecked()) {
|
||||||
preferences.add("0");
|
preferences.add("0");
|
||||||
@@ -127,7 +111,7 @@ public class PasswordGeneratorDialogFragment extends DialogFragment {
|
|||||||
try {
|
try {
|
||||||
int length = Integer.valueOf(editText.getText().toString());
|
int length = Integer.valueOf(editText.getText().toString());
|
||||||
PasswordGenerator.INSTANCE.setPrefs(getActivity().getApplicationContext(), preferences, length);
|
PasswordGenerator.INSTANCE.setPrefs(getActivity().getApplicationContext(), preferences, length);
|
||||||
} catch(NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
PasswordGenerator.INSTANCE.setPrefs(getActivity().getApplicationContext(), preferences);
|
PasswordGenerator.INSTANCE.setPrefs(getActivity().getApplicationContext(), preferences);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ package com.zeapo.pwdstore;
|
|||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
@@ -14,20 +13,6 @@ import android.graphics.drawable.Icon;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
|
||||||
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.core.view.MenuItemCompat;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.appcompat.widget.SearchView;
|
|
||||||
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@@ -35,16 +20,23 @@ import android.view.Menu;
|
|||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.SearchView;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.view.MenuItemCompat;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.zeapo.pwdstore.crypto.PgpActivity;
|
import com.zeapo.pwdstore.crypto.PgpActivity;
|
||||||
import com.zeapo.pwdstore.git.GitActivity;
|
import com.zeapo.pwdstore.git.GitActivity;
|
||||||
import com.zeapo.pwdstore.git.GitAsyncTask;
|
import com.zeapo.pwdstore.git.GitAsyncTask;
|
||||||
import com.zeapo.pwdstore.git.GitOperation;
|
import com.zeapo.pwdstore.git.GitOperation;
|
||||||
import com.zeapo.pwdstore.pwgen.PRNGFixes;
|
|
||||||
import com.zeapo.pwdstore.utils.PasswordItem;
|
import com.zeapo.pwdstore.utils.PasswordItem;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRecyclerAdapter;
|
import com.zeapo.pwdstore.utils.PasswordRecyclerAdapter;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.io.FilenameUtils;
|
import org.apache.commons.io.FilenameUtils;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
@@ -53,21 +45,15 @@ import org.eclipse.jgit.lib.Repository;
|
|||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class PasswordStore extends AppCompatActivity {
|
public class PasswordStore extends AppCompatActivity {
|
||||||
|
|
||||||
private static final String TAG = PasswordStore.class.getName();
|
|
||||||
private SharedPreferences settings;
|
|
||||||
private Activity activity;
|
|
||||||
private PasswordFragment plist;
|
|
||||||
private ShortcutManager shortcutManager;
|
|
||||||
private MenuItem searchItem = null;
|
|
||||||
private SearchView searchView;
|
|
||||||
|
|
||||||
private final static int CLONE_REPO_BUTTON = 401;
|
|
||||||
private final static int NEW_REPO_BUTTON = 402;
|
|
||||||
private final static int HOME = 403;
|
|
||||||
public static final int REQUEST_CODE_SIGN = 9910;
|
public static final int REQUEST_CODE_SIGN = 9910;
|
||||||
public static final int REQUEST_CODE_ENCRYPT = 9911;
|
public static final int REQUEST_CODE_ENCRYPT = 9911;
|
||||||
public static final int REQUEST_CODE_SIGN_AND_ENCRYPT = 9912;
|
public static final int REQUEST_CODE_SIGN_AND_ENCRYPT = 9912;
|
||||||
@@ -76,6 +62,17 @@ public class PasswordStore extends AppCompatActivity {
|
|||||||
public static final int REQUEST_CODE_GET_KEY_IDS = 9915;
|
public static final int REQUEST_CODE_GET_KEY_IDS = 9915;
|
||||||
public static final int REQUEST_CODE_EDIT = 9916;
|
public static final int REQUEST_CODE_EDIT = 9916;
|
||||||
public static final int REQUEST_CODE_SELECT_FOLDER = 9917;
|
public static final int REQUEST_CODE_SELECT_FOLDER = 9917;
|
||||||
|
private static final String TAG = PasswordStore.class.getName();
|
||||||
|
private final static int CLONE_REPO_BUTTON = 401;
|
||||||
|
private final static int NEW_REPO_BUTTON = 402;
|
||||||
|
private final static int HOME = 403;
|
||||||
|
private final static int REQUEST_EXTERNAL_STORAGE = 50;
|
||||||
|
private SharedPreferences settings;
|
||||||
|
private Activity activity;
|
||||||
|
private PasswordFragment plist;
|
||||||
|
private ShortcutManager shortcutManager;
|
||||||
|
private MenuItem searchItem = null;
|
||||||
|
private SearchView searchView;
|
||||||
|
|
||||||
private static boolean isPrintable(char c) {
|
private static boolean isPrintable(char c) {
|
||||||
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
|
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
|
||||||
@@ -106,8 +103,6 @@ public class PasswordStore extends AppCompatActivity {
|
|||||||
return super.onKeyDown(keyCode, event);
|
return super.onKeyDown(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final static int REQUEST_EXTERNAL_STORAGE = 50;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -116,7 +111,6 @@ public class PasswordStore extends AppCompatActivity {
|
|||||||
shortcutManager = getSystemService(ShortcutManager.class);
|
shortcutManager = getSystemService(ShortcutManager.class);
|
||||||
}
|
}
|
||||||
activity = this;
|
activity = this;
|
||||||
PRNGFixes.INSTANCE.apply();
|
|
||||||
|
|
||||||
// If user opens app with permission granted then revokes and returns,
|
// If user opens app with permission granted then revokes and returns,
|
||||||
// prevent attempt to create password list fragment
|
// prevent attempt to create password list fragment
|
||||||
@@ -142,17 +136,12 @@ public class PasswordStore extends AppCompatActivity {
|
|||||||
// TODO: strings.xml
|
// TODO: strings.xml
|
||||||
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 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)
|
Snackbar.LENGTH_INDEFINITE)
|
||||||
.setAction(R.string.dialog_ok, new View.OnClickListener() {
|
.setAction(R.string.dialog_ok, view -> ActivityCompat.requestPermissions(activity,
|
||||||
@Override
|
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||||
public void onClick(View view) {
|
REQUEST_EXTERNAL_STORAGE));
|
||||||
ActivityCompat.requestPermissions(activity,
|
|
||||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
|
||||||
REQUEST_EXTERNAL_STORAGE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
snack.show();
|
snack.show();
|
||||||
View view = snack.getView();
|
View view = snack.getView();
|
||||||
TextView tv = (TextView) view.findViewById(com.google.android.material.R.id.snackbar_text);
|
TextView tv = view.findViewById(com.google.android.material.R.id.snackbar_text);
|
||||||
tv.setTextColor(Color.WHITE);
|
tv.setTextColor(Color.WHITE);
|
||||||
tv.setMaxLines(10);
|
tv.setMaxLines(10);
|
||||||
} else {
|
} else {
|
||||||
@@ -357,12 +346,9 @@ public class PasswordStore extends AppCompatActivity {
|
|||||||
if (keyIds.isEmpty())
|
if (keyIds.isEmpty())
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setMessage(this.getResources().getString(R.string.key_dialog_text))
|
.setMessage(this.getResources().getString(R.string.key_dialog_text))
|
||||||
.setPositiveButton(this.getResources().getString(R.string.dialog_positive), new DialogInterface.OnClickListener() {
|
.setPositiveButton(this.getResources().getString(R.string.dialog_positive), (dialogInterface, i) -> {
|
||||||
@Override
|
Intent intent = new Intent(activity, UserPreference.class);
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
startActivityForResult(intent, GitActivity.REQUEST_INIT);
|
||||||
Intent intent = new Intent(activity, UserPreference.class);
|
|
||||||
startActivityForResult(intent, GitActivity.REQUEST_INIT);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.setNegativeButton(this.getResources().getString(R.string.dialog_negative), null)
|
.setNegativeButton(this.getResources().getString(R.string.dialog_negative), null)
|
||||||
.show();
|
.show();
|
||||||
@@ -516,24 +502,18 @@ public class PasswordStore extends AppCompatActivity {
|
|||||||
if (!PasswordRepository.isInitialized()) {
|
if (!PasswordRepository.isInitialized()) {
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setMessage(this.getResources().getString(R.string.creation_dialog_text))
|
.setMessage(this.getResources().getString(R.string.creation_dialog_text))
|
||||||
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
|
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
}
|
|
||||||
}).show();
|
}).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.getStringSet("openpgp_key_ids_set", new HashSet<String>()).isEmpty()) {
|
if (settings.getStringSet("openpgp_key_ids_set", new HashSet<>()).isEmpty()) {
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setTitle(this.getResources().getString(R.string.no_key_selected_dialog_title))
|
.setTitle(this.getResources().getString(R.string.no_key_selected_dialog_title))
|
||||||
.setMessage(this.getResources().getString(R.string.no_key_selected_dialog_text))
|
.setMessage(this.getResources().getString(R.string.no_key_selected_dialog_text))
|
||||||
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
|
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
|
||||||
@Override
|
Intent intent = new Intent(activity, UserPreference.class);
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
startActivity(intent);
|
||||||
Intent intent = new Intent(activity, UserPreference.class);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
}).show();
|
}).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -559,25 +539,19 @@ public class PasswordStore extends AppCompatActivity {
|
|||||||
new AlertDialog.Builder(this).
|
new AlertDialog.Builder(this).
|
||||||
setMessage(this.getResources().getString(R.string.delete_dialog_text) +
|
setMessage(this.getResources().getString(R.string.delete_dialog_text) +
|
||||||
item + "\"")
|
item + "\"")
|
||||||
.setPositiveButton(this.getResources().getString(R.string.dialog_yes), new DialogInterface.OnClickListener() {
|
.setPositiveButton(this.getResources().getString(R.string.dialog_yes), (dialogInterface, i) -> {
|
||||||
@Override
|
item.getFile().delete();
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
adapter.remove(position);
|
||||||
item.getFile().delete();
|
it.remove();
|
||||||
adapter.remove(position);
|
adapter.updateSelectedItems(position, selectedItems);
|
||||||
it.remove();
|
|
||||||
adapter.updateSelectedItems(position, selectedItems);
|
|
||||||
|
|
||||||
commitChange(getResources().getString(R.string.git_commit_remove_text,
|
commitChange(getResources().getString(R.string.git_commit_remove_text,
|
||||||
item.getLongName()));
|
item.getLongName()));
|
||||||
deletePasswords(adapter, selectedItems);
|
deletePasswords(adapter, selectedItems);
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.setNegativeButton(this.getResources().getString(R.string.dialog_no), new DialogInterface.OnClickListener() {
|
.setNegativeButton(this.getResources().getString(R.string.dialog_no), (dialogInterface, i) -> {
|
||||||
@Override
|
it.remove();
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
deletePasswords(adapter, selectedItems);
|
||||||
it.remove();
|
|
||||||
deletePasswords(adapter, selectedItems);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
@@ -771,64 +745,54 @@ public class PasswordStore extends AppCompatActivity {
|
|||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setTitle(this.getResources().getString(R.string.location_dialog_title))
|
.setTitle(this.getResources().getString(R.string.location_dialog_title))
|
||||||
.setMessage(this.getResources().getString(R.string.location_dialog_text))
|
.setMessage(this.getResources().getString(R.string.location_dialog_text))
|
||||||
.setPositiveButton(this.getResources().getString(R.string.location_hidden), new DialogInterface.OnClickListener() {
|
.setPositiveButton(this.getResources().getString(R.string.location_hidden), (dialog, whichButton) -> {
|
||||||
public void onClick(DialogInterface dialog, int whichButton) {
|
settings.edit().putBoolean("git_external", false).apply();
|
||||||
settings.edit().putBoolean("git_external", false).apply();
|
|
||||||
|
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case NEW_REPO_BUTTON:
|
case NEW_REPO_BUTTON:
|
||||||
initializeRepositoryInfo();
|
initializeRepositoryInfo();
|
||||||
break;
|
break;
|
||||||
case CLONE_REPO_BUTTON:
|
case CLONE_REPO_BUTTON:
|
||||||
PasswordRepository.initialize(PasswordStore.this);
|
PasswordRepository.initialize(PasswordStore.this);
|
||||||
|
|
||||||
Intent intent = new Intent(activity, GitActivity.class);
|
Intent intent = new Intent(activity, GitActivity.class);
|
||||||
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
|
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
|
||||||
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
|
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton(this.getResources().getString(R.string.location_sdcard), new DialogInterface.OnClickListener() {
|
.setNegativeButton(this.getResources().getString(R.string.location_sdcard), (dialog, whichButton) -> {
|
||||||
public void onClick(DialogInterface dialog, int whichButton) {
|
settings.edit().putBoolean("git_external", true).apply();
|
||||||
settings.edit().putBoolean("git_external", true).apply();
|
|
||||||
|
|
||||||
String externalRepo = settings.getString("git_external_repo", null);
|
String externalRepo = settings.getString("git_external_repo", null);
|
||||||
|
|
||||||
if (externalRepo == null) {
|
if (externalRepo == null) {
|
||||||
Intent intent = new Intent(activity, UserPreference.class);
|
Intent intent = new Intent(activity, UserPreference.class);
|
||||||
intent.putExtra("operation", "git_external");
|
intent.putExtra("operation", "git_external");
|
||||||
startActivityForResult(intent, operation);
|
startActivityForResult(intent, operation);
|
||||||
} else {
|
} else {
|
||||||
new AlertDialog.Builder(activity)
|
new AlertDialog.Builder(activity)
|
||||||
.setTitle(getResources().getString(R.string.directory_selected_title))
|
.setTitle(getResources().getString(R.string.directory_selected_title))
|
||||||
.setMessage(getResources().getString(R.string.directory_selected_message, externalRepo))
|
.setMessage(getResources().getString(R.string.directory_selected_message, externalRepo))
|
||||||
.setPositiveButton(getResources().getString(R.string.use), new DialogInterface.OnClickListener() {
|
.setPositiveButton(getResources().getString(R.string.use), (dialog1, which) -> {
|
||||||
@Override
|
switch (operation) {
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
case NEW_REPO_BUTTON:
|
||||||
switch (operation) {
|
initializeRepositoryInfo();
|
||||||
case NEW_REPO_BUTTON:
|
break;
|
||||||
initializeRepositoryInfo();
|
case CLONE_REPO_BUTTON:
|
||||||
break;
|
PasswordRepository.initialize(PasswordStore.this);
|
||||||
case CLONE_REPO_BUTTON:
|
|
||||||
PasswordRepository.initialize(PasswordStore.this);
|
|
||||||
|
|
||||||
Intent intent = new Intent(activity, GitActivity.class);
|
Intent intent = new Intent(activity, GitActivity.class);
|
||||||
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
|
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
|
||||||
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
|
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
})
|
.setNegativeButton(getResources().getString(R.string.change), (dialog12, which) -> {
|
||||||
.setNegativeButton(getResources().getString(R.string.change), new DialogInterface.OnClickListener() {
|
Intent intent = new Intent(activity, UserPreference.class);
|
||||||
@Override
|
intent.putExtra("operation", "git_external");
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
startActivityForResult(intent, operation);
|
||||||
Intent intent = new Intent(activity, UserPreference.class);
|
}).show();
|
||||||
intent.putExtra("operation", "git_external");
|
|
||||||
startActivityForResult(intent, operation);
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
|
@@ -2,9 +2,9 @@ package com.zeapo.pwdstore
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository
|
import com.zeapo.pwdstore.utils.PasswordRepository
|
||||||
|
|
||||||
|
@@ -1,50 +1,44 @@
|
|||||||
package com.zeapo.pwdstore;
|
package com.zeapo.pwdstore;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
||||||
import androidx.fragment.app.Fragment;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.zeapo.pwdstore.utils.FolderRecyclerAdapter;
|
import com.zeapo.pwdstore.utils.FolderRecyclerAdapter;
|
||||||
import com.zeapo.pwdstore.utils.PasswordItem;
|
import com.zeapo.pwdstore.utils.PasswordItem;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment representing a list of Items.
|
* A fragment representing a list of Items.
|
||||||
* <p />
|
* <p/>
|
||||||
* Large screen devices (such as tablets) are supported by replacing the ListView
|
* Large screen devices (such as tablets) are supported by replacing the ListView
|
||||||
* with a GridView.
|
* with a GridView.
|
||||||
* <p />
|
* <p/>
|
||||||
*/
|
*/
|
||||||
public class SelectFolderFragment extends Fragment{
|
public class SelectFolderFragment extends Fragment {
|
||||||
|
|
||||||
public interface OnFragmentInteractionListener {
|
|
||||||
void onFragmentInteraction(PasswordItem item);
|
|
||||||
}
|
|
||||||
|
|
||||||
// store the pass files list in a stack
|
// store the pass files list in a stack
|
||||||
private Stack<File> pathStack;
|
private Stack<File> pathStack;
|
||||||
private FolderRecyclerAdapter recyclerAdapter;
|
private FolderRecyclerAdapter recyclerAdapter;
|
||||||
private RecyclerView recyclerView;
|
private RecyclerView recyclerView;
|
||||||
private OnFragmentInteractionListener mListener;
|
private OnFragmentInteractionListener mListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||||
* fragment (e.g. upon screen orientation changes).
|
* fragment (e.g. upon screen orientation changes).
|
||||||
*/
|
*/
|
||||||
public SelectFolderFragment() { }
|
public SelectFolderFragment() {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -53,11 +47,11 @@ public class SelectFolderFragment extends Fragment{
|
|||||||
|
|
||||||
pathStack = new Stack<>();
|
pathStack = new Stack<>();
|
||||||
recyclerAdapter = new FolderRecyclerAdapter((SelectFolderActivity) getActivity(), mListener,
|
recyclerAdapter = new FolderRecyclerAdapter((SelectFolderActivity) getActivity(), mListener,
|
||||||
PasswordRepository.getPasswords(new File(path), PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()));
|
PasswordRepository.getPasswords(new File(path), PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.password_recycler_view, container, false);
|
View view = inflater.inflate(R.layout.password_recycler_view, container, false);
|
||||||
|
|
||||||
@@ -81,28 +75,27 @@ public class SelectFolderFragment extends Fragment{
|
|||||||
public void onAttach(final Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
try {
|
try {
|
||||||
mListener = new OnFragmentInteractionListener() {
|
mListener = item -> {
|
||||||
public void onFragmentInteraction(PasswordItem item) {
|
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
|
||||||
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
|
//push the category were we're going
|
||||||
//push the category were we're going
|
pathStack.push(item.getFile());
|
||||||
pathStack.push(item.getFile());
|
|
||||||
|
|
||||||
recyclerView.scrollToPosition(0);
|
recyclerView.scrollToPosition(0);
|
||||||
recyclerAdapter.clear();
|
recyclerAdapter.clear();
|
||||||
recyclerAdapter.addAll(PasswordRepository.getPasswords(item.getFile(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
|
recyclerAdapter.addAll(PasswordRepository.getPasswords(item.getFile(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
|
||||||
|
|
||||||
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
throw new ClassCastException(context.toString()
|
throw new ClassCastException(context.toString()
|
||||||
+ " must implement OnFragmentInteractionListener");
|
+ " must implement OnFragmentInteractionListener");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* gets the current directory
|
* gets the current directory
|
||||||
|
*
|
||||||
* @return the current directory
|
* @return the current directory
|
||||||
*/
|
*/
|
||||||
public File getCurrentDir() {
|
public File getCurrentDir() {
|
||||||
@@ -115,4 +108,8 @@ public class SelectFolderFragment extends Fragment{
|
|||||||
private PasswordRepository.PasswordSortOrder getSortOrder() {
|
private PasswordRepository.PasswordSortOrder getSortOrder() {
|
||||||
return PasswordRepository.PasswordSortOrder.getSortOrder(PreferenceManager.getDefaultSharedPreferences(getActivity()));
|
return PasswordRepository.PasswordSortOrder.getSortOrder(PreferenceManager.getDefaultSharedPreferences(getActivity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface OnFragmentInteractionListener {
|
||||||
|
void onFragmentInteraction(PasswordItem item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,14 +8,11 @@ import android.app.ProgressDialog;
|
|||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -24,15 +21,14 @@ import android.view.inputmethod.InputMethodManager;
|
|||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.CompoundButton;
|
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import com.jcraft.jsch.JSch;
|
import com.jcraft.jsch.JSch;
|
||||||
import com.jcraft.jsch.KeyPair;
|
import com.jcraft.jsch.KeyPair;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -41,6 +37,34 @@ import java.lang.ref.WeakReference;
|
|||||||
|
|
||||||
public class SshKeyGen extends AppCompatActivity {
|
public class SshKeyGen extends AppCompatActivity {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
if (getSupportActionBar() != null)
|
||||||
|
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
|
setTitle("Generate SSH Key");
|
||||||
|
|
||||||
|
if (savedInstanceState == null) {
|
||||||
|
getFragmentManager().beginTransaction()
|
||||||
|
.replace(android.R.id.content, new SshKeyGenFragment()).commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when 'Generate' button of SshKeyGenFragment clicked. Generates a
|
||||||
|
// private and public key, then replaces the SshKeyGenFragment with a
|
||||||
|
// ShowSshKeyFragment which displays the public key.
|
||||||
|
public void generate(View view) {
|
||||||
|
String length = Integer.toString((Integer) ((Spinner) findViewById(R.id.length)).getSelectedItem());
|
||||||
|
String passphrase = ((EditText) findViewById(R.id.passphrase)).getText().toString();
|
||||||
|
String comment = ((EditText) findViewById(R.id.comment)).getText().toString();
|
||||||
|
new KeyGenerateTask(this).execute(length, passphrase, comment);
|
||||||
|
|
||||||
|
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
// SSH key generation UI
|
// SSH key generation UI
|
||||||
public static class SshKeyGenFragment extends Fragment {
|
public static class SshKeyGenFragment extends Fragment {
|
||||||
public SshKeyGenFragment() {
|
public SshKeyGenFragment() {
|
||||||
@@ -61,18 +85,15 @@ public class SshKeyGen extends AppCompatActivity {
|
|||||||
((EditText) v.findViewById(R.id.passphrase)).setTypeface(monoTypeface);
|
((EditText) v.findViewById(R.id.passphrase)).setTypeface(monoTypeface);
|
||||||
|
|
||||||
CheckBox checkbox = v.findViewById(R.id.show_passphrase);
|
CheckBox checkbox = v.findViewById(R.id.show_passphrase);
|
||||||
checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
checkbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||||
@Override
|
EditText editText = v.findViewById(R.id.passphrase);
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
int selection = editText.getSelectionEnd();
|
||||||
EditText editText = v.findViewById(R.id.passphrase);
|
if (isChecked) {
|
||||||
int selection = editText.getSelectionEnd();
|
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
|
||||||
if (isChecked) {
|
} else {
|
||||||
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
|
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
} else {
|
|
||||||
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
|
||||||
}
|
|
||||||
editText.setSelection(selection);
|
|
||||||
}
|
}
|
||||||
|
editText.setSelection(selection);
|
||||||
});
|
});
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
@@ -100,59 +121,31 @@ public class SshKeyGen extends AppCompatActivity {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setPositiveButton(getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
|
builder.setPositiveButton(getResources().getString(R.string.dialog_ok), (dialog, which) -> {
|
||||||
@Override
|
if (getActivity() instanceof SshKeyGen)
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
getActivity().finish();
|
||||||
if (getActivity() instanceof SshKeyGen)
|
|
||||||
getActivity().finish();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.setNegativeButton(getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
|
builder.setNegativeButton(getResources().getString(R.string.dialog_cancel), (dialog, which) -> {
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.setNeutralButton(getResources().getString(R.string.ssh_keygen_copy), null);
|
builder.setNeutralButton(getResources().getString(R.string.ssh_keygen_copy), null);
|
||||||
|
|
||||||
final AlertDialog ad = builder.setTitle("Your public key").create();
|
final AlertDialog ad = builder.setTitle("Your public key").create();
|
||||||
ad.setOnShowListener(new DialogInterface.OnShowListener() {
|
ad.setOnShowListener(dialog -> {
|
||||||
@Override
|
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
|
||||||
public void onShow(DialogInterface dialog) {
|
b.setOnClickListener(v1 -> {
|
||||||
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
|
TextView textView1 = getDialog().findViewById(R.id.public_key);
|
||||||
b.setOnClickListener(new View.OnClickListener() {
|
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
@Override
|
ClipData clip = ClipData.newPlainText("public key", textView1.getText().toString());
|
||||||
public void onClick(View v) {
|
clipboard.setPrimaryClip(clip);
|
||||||
TextView textView = getDialog().findViewById(R.id.public_key);
|
});
|
||||||
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
|
|
||||||
ClipData clip = ClipData.newPlainText("public key", textView.getText().toString());
|
|
||||||
clipboard.setPrimaryClip(clip);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return ad;
|
return ad;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
if (getSupportActionBar() != null)
|
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
setTitle("Generate SSH Key");
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
getFragmentManager().beginTransaction()
|
|
||||||
.replace(android.R.id.content, new SshKeyGenFragment()).commit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class KeyGenerateTask extends AsyncTask<String, Void, Exception> {
|
private static class KeyGenerateTask extends AsyncTask<String, Void, Exception> {
|
||||||
private ProgressDialog pd;
|
private ProgressDialog pd;
|
||||||
private WeakReference<SshKeyGen> weakReference;
|
private WeakReference<SshKeyGen> weakReference;
|
||||||
@@ -211,27 +204,11 @@ public class SshKeyGen extends AppCompatActivity {
|
|||||||
new AlertDialog.Builder(weakReference.get())
|
new AlertDialog.Builder(weakReference.get())
|
||||||
.setTitle("Error while trying to generate the ssh-key")
|
.setTitle("Error while trying to generate the ssh-key")
|
||||||
.setMessage(weakReference.get().getResources().getString(R.string.ssh_key_error_dialog_text) + e.getMessage())
|
.setMessage(weakReference.get().getResources().getString(R.string.ssh_key_error_dialog_text) + e.getMessage())
|
||||||
.setPositiveButton(weakReference.get().getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
|
.setPositiveButton(weakReference.get().getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
|
||||||
@Override
|
// pass
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invoked when 'Generate' button of SshKeyGenFragment clicked. Generates a
|
|
||||||
// private and public key, then replaces the SshKeyGenFragment with a
|
|
||||||
// ShowSshKeyFragment which displays the public key.
|
|
||||||
public void generate(View view) {
|
|
||||||
String length = Integer.toString((Integer) ((Spinner) findViewById(R.id.length)).getSelectedItem());
|
|
||||||
String passphrase = ((EditText) findViewById(R.id.passphrase)).getText().toString();
|
|
||||||
String comment = ((EditText) findViewById(R.id.comment)).getText().toString();
|
|
||||||
new KeyGenerateTask(this).execute(length, passphrase, comment);
|
|
||||||
|
|
||||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
|
||||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,18 @@
|
|||||||
package com.zeapo.pwdstore
|
package com.zeapo.pwdstore
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
|
||||||
class ToCloneOrNot : Fragment() {
|
class ToCloneOrNot : Fragment() {
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(
|
||||||
savedInstanceState: Bundle?): View? {
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
// Inflate the layout for this fragment
|
// Inflate the layout for this fragment
|
||||||
return inflater.inflate(R.layout.fragment_to_clone_or_not, container, false)
|
return inflater.inflate(R.layout.fragment_to_clone_or_not, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -15,16 +15,16 @@ import android.preference.Preference
|
|||||||
import android.preference.PreferenceFragment
|
import android.preference.PreferenceFragment
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import com.google.android.material.snackbar.Snackbar
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.accessibility.AccessibilityManager
|
import android.view.accessibility.AccessibilityManager
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.nononsenseapps.filepicker.FilePickerActivity
|
import com.nononsenseapps.filepicker.FilePickerActivity
|
||||||
import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity
|
import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity
|
||||||
import com.zeapo.pwdstore.crypto.PgpActivity
|
import com.zeapo.pwdstore.crypto.PgpActivity
|
||||||
@@ -36,8 +36,9 @@ import org.openintents.openpgp.util.OpenPgpUtils
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.ArrayList
|
||||||
import kotlin.collections.HashSet
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class UserPreference : AppCompatActivity() {
|
class UserPreference : AppCompatActivity() {
|
||||||
private lateinit var prefsFragment: PrefsFragment
|
private lateinit var prefsFragment: PrefsFragment
|
||||||
@@ -75,17 +76,19 @@ class UserPreference : AppCompatActivity() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
findPreference("ssh_key_clear_passphrase").onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
findPreference("ssh_key_clear_passphrase").onPreferenceClickListener =
|
||||||
sharedPreferences.edit().putString("ssh_key_passphrase", null).apply()
|
Preference.OnPreferenceClickListener {
|
||||||
it.isEnabled = false
|
sharedPreferences.edit().putString("ssh_key_passphrase", null).apply()
|
||||||
true
|
it.isEnabled = false
|
||||||
}
|
true
|
||||||
|
}
|
||||||
|
|
||||||
findPreference("hotp_remember_clear_choice").onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
findPreference("hotp_remember_clear_choice").onPreferenceClickListener =
|
||||||
sharedPreferences.edit().putBoolean("hotp_remember_check", false).apply()
|
Preference.OnPreferenceClickListener {
|
||||||
it.isEnabled = false
|
sharedPreferences.edit().putBoolean("hotp_remember_check", false).apply()
|
||||||
true
|
it.isEnabled = false
|
||||||
}
|
true
|
||||||
|
}
|
||||||
|
|
||||||
findPreference("git_server_info").onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
findPreference("git_server_info").onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
val intent = Intent(callingActivity, GitActivity::class.java)
|
val intent = Intent(callingActivity, GitActivity::class.java)
|
||||||
@@ -104,28 +107,30 @@ class UserPreference : AppCompatActivity() {
|
|||||||
findPreference("git_delete_repo").onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
findPreference("git_delete_repo").onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
val repoDir = PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext)
|
val repoDir = PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext)
|
||||||
AlertDialog.Builder(callingActivity)
|
AlertDialog.Builder(callingActivity)
|
||||||
.setTitle(R.string.pref_dialog_delete_title)
|
.setTitle(R.string.pref_dialog_delete_title)
|
||||||
.setMessage("${resources.getString(R.string.dialog_delete_msg)} \n $repoDir")
|
.setMessage("${resources.getString(R.string.dialog_delete_msg)} \n $repoDir")
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(R.string.dialog_delete) { dialogInterface, _ ->
|
.setPositiveButton(R.string.dialog_delete) { dialogInterface, _ ->
|
||||||
try {
|
try {
|
||||||
FileUtils.cleanDirectory(PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext))
|
FileUtils.cleanDirectory(PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext))
|
||||||
PasswordRepository.closeRepository()
|
PasswordRepository.closeRepository()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
//TODO Handle the diffent cases of exceptions
|
//TODO Handle the diffent cases of exceptions
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedPreferences.edit().putBoolean("repository_initialized", false).apply()
|
sharedPreferences.edit().putBoolean("repository_initialized", false).apply()
|
||||||
dialogInterface.cancel()
|
dialogInterface.cancel()
|
||||||
callingActivity.finish()
|
callingActivity.finish()
|
||||||
}.setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ -> run { dialogInterface.cancel() } }
|
}
|
||||||
.show()
|
.setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ -> run { dialogInterface.cancel() } }
|
||||||
|
.show()
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
val externalRepo = findPreference("pref_select_external")
|
val externalRepo = findPreference("pref_select_external")
|
||||||
externalRepo.summary = sharedPreferences.getString("git_external_repo", callingActivity.getString(R.string.no_repo_selected))
|
externalRepo.summary =
|
||||||
|
sharedPreferences.getString("git_external_repo", callingActivity.getString(R.string.no_repo_selected))
|
||||||
externalRepo.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
externalRepo.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
callingActivity.selectExternalGitRepository()
|
callingActivity.selectExternalGitRepository()
|
||||||
true
|
true
|
||||||
@@ -148,10 +153,14 @@ class UserPreference : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findPreference("autofill_enable").onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
findPreference("autofill_enable").onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||||
AlertDialog.Builder(callingActivity).setTitle(R.string.pref_autofill_enable_title).setView(R.layout.autofill_instructions).setPositiveButton(R.string.dialog_ok) { _, _ ->
|
AlertDialog.Builder(callingActivity).setTitle(R.string.pref_autofill_enable_title)
|
||||||
|
.setView(R.layout.autofill_instructions).setPositiveButton(R.string.dialog_ok) { _, _ ->
|
||||||
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
|
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}.setNegativeButton(R.string.dialog_cancel, null).setOnDismissListener { (findPreference("autofill_enable") as CheckBoxPreference).isChecked = (activity as UserPreference).isServiceEnabled }.show()
|
}.setNegativeButton(R.string.dialog_cancel, null).setOnDismissListener {
|
||||||
|
(findPreference("autofill_enable") as CheckBoxPreference).isChecked =
|
||||||
|
(activity as UserPreference).isServiceEnabled
|
||||||
|
}.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,24 +173,34 @@ class UserPreference : AppCompatActivity() {
|
|||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
val sharedPreferences = preferenceManager.sharedPreferences
|
val sharedPreferences = preferenceManager.sharedPreferences
|
||||||
findPreference("pref_select_external").summary = preferenceManager.sharedPreferences.getString("git_external_repo", getString(R.string.no_repo_selected))
|
findPreference("pref_select_external").summary =
|
||||||
|
preferenceManager.sharedPreferences.getString("git_external_repo", getString(R.string.no_repo_selected))
|
||||||
findPreference("ssh_see_key").isEnabled = sharedPreferences.getBoolean("use_generated_key", false)
|
findPreference("ssh_see_key").isEnabled = sharedPreferences.getBoolean("use_generated_key", false)
|
||||||
findPreference("git_delete_repo").isEnabled = !sharedPreferences.getBoolean("git_external", false)
|
findPreference("git_delete_repo").isEnabled = !sharedPreferences.getBoolean("git_external", false)
|
||||||
findPreference("ssh_key_clear_passphrase").isEnabled = sharedPreferences.getString("ssh_key_passphrase", null)?.isNotEmpty() ?: false
|
findPreference("ssh_key_clear_passphrase").isEnabled = sharedPreferences.getString(
|
||||||
findPreference("hotp_remember_clear_choice").isEnabled = sharedPreferences.getBoolean("hotp_remember_check", false)
|
"ssh_key_passphrase",
|
||||||
|
null
|
||||||
|
)?.isNotEmpty() ?: false
|
||||||
|
findPreference("hotp_remember_clear_choice").isEnabled =
|
||||||
|
sharedPreferences.getBoolean("hotp_remember_check", false)
|
||||||
val keyPref = findPreference("openpgp_key_id_pref")
|
val keyPref = findPreference("openpgp_key_id_pref")
|
||||||
val selectedKeys: Array<String> = ArrayList<String>(sharedPreferences.getStringSet("openpgp_key_ids_set", HashSet<String>())).toTypedArray()
|
val selectedKeys: Array<String> = ArrayList<String>(
|
||||||
|
sharedPreferences.getStringSet(
|
||||||
|
"openpgp_key_ids_set",
|
||||||
|
HashSet<String>()
|
||||||
|
)
|
||||||
|
).toTypedArray()
|
||||||
if (selectedKeys.isEmpty()) {
|
if (selectedKeys.isEmpty()) {
|
||||||
keyPref.summary = this.resources.getString(R.string.pref_no_key_selected)
|
keyPref.summary = this.resources.getString(R.string.pref_no_key_selected)
|
||||||
} else {
|
} else {
|
||||||
keyPref.summary = selectedKeys.joinToString(separator = ";") {
|
keyPref.summary = selectedKeys.joinToString(separator = ";") { s ->
|
||||||
s ->
|
|
||||||
OpenPgpUtils.convertKeyIdToHex(java.lang.Long.valueOf(s))
|
OpenPgpUtils.convertKeyIdToHex(java.lang.Long.valueOf(s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// see if the autofill service is enabled and check the preference accordingly
|
// see if the autofill service is enabled and check the preference accordingly
|
||||||
(findPreference("autofill_enable") as CheckBoxPreference).isChecked = (activity as UserPreference).isServiceEnabled
|
(findPreference("autofill_enable") as CheckBoxPreference).isChecked =
|
||||||
|
(activity as UserPreference).isServiceEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,24 +222,23 @@ class UserPreference : AppCompatActivity() {
|
|||||||
fun selectExternalGitRepository() {
|
fun selectExternalGitRepository() {
|
||||||
val activity = this
|
val activity = this
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle(this.resources.getString(R.string.external_repository_dialog_title))
|
.setTitle(this.resources.getString(R.string.external_repository_dialog_title))
|
||||||
.setMessage(this.resources.getString(R.string.external_repository_dialog_text))
|
.setMessage(this.resources.getString(R.string.external_repository_dialog_text))
|
||||||
.setPositiveButton(R.string.dialog_ok) { _, _ ->
|
.setPositiveButton(R.string.dialog_ok) { _, _ ->
|
||||||
// This always works
|
// This always works
|
||||||
val i = Intent(activity.applicationContext, FilePickerActivity::class.java)
|
val i = Intent(activity.applicationContext, FilePickerActivity::class.java)
|
||||||
// This works if you defined the intent filter
|
// This works if you defined the intent filter
|
||||||
// Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
// Intent i = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
|
|
||||||
// Set these depending on your use case. These are the defaults.
|
// Set these depending on your use case. These are the defaults.
|
||||||
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
|
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
|
||||||
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
|
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
|
||||||
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
|
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
|
||||||
|
|
||||||
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().path)
|
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().path)
|
||||||
|
|
||||||
startActivityForResult(i, SELECT_GIT_DIRECTORY)
|
|
||||||
}.setNegativeButton(R.string.dialog_cancel, null).show()
|
|
||||||
|
|
||||||
|
startActivityForResult(i, SELECT_GIT_DIRECTORY)
|
||||||
|
}.setNegativeButton(R.string.dialog_cancel, null).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
@@ -242,9 +260,9 @@ class UserPreference : AppCompatActivity() {
|
|||||||
* Opens a file explorer to import the private key
|
* Opens a file explorer to import the private key
|
||||||
*/
|
*/
|
||||||
fun getSshKeyWithPermissions(useDefaultPicker: Boolean) = runWithPermissions(
|
fun getSshKeyWithPermissions(useDefaultPicker: Boolean) = runWithPermissions(
|
||||||
requestedPermission = Manifest.permission.READ_EXTERNAL_STORAGE,
|
requestedPermission = Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||||
requestCode = REQUEST_EXTERNAL_STORAGE_SSH_KEY,
|
requestCode = REQUEST_EXTERNAL_STORAGE_SSH_KEY,
|
||||||
reason = "We need access to the sd-card to import the ssh-key"
|
reason = "We need access to the sd-card to import the ssh-key"
|
||||||
) {
|
) {
|
||||||
getSshKey(useDefaultPicker)
|
getSshKey(useDefaultPicker)
|
||||||
}
|
}
|
||||||
@@ -282,9 +300,9 @@ class UserPreference : AppCompatActivity() {
|
|||||||
if (ContextCompat.checkSelfPermission(this, requestedPermission) != PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(this, requestedPermission) != PackageManager.PERMISSION_GRANTED) {
|
||||||
if (ActivityCompat.shouldShowRequestPermissionRationale(this, requestedPermission)) {
|
if (ActivityCompat.shouldShowRequestPermissionRationale(this, requestedPermission)) {
|
||||||
val snack = Snackbar.make(prefsFragment.view, reason, Snackbar.LENGTH_INDEFINITE)
|
val snack = Snackbar.make(prefsFragment.view, reason, Snackbar.LENGTH_INDEFINITE)
|
||||||
.setAction(R.string.dialog_ok) {
|
.setAction(R.string.dialog_ok) {
|
||||||
ActivityCompat.requestPermissions(this, arrayOf(requestedPermission), requestCode)
|
ActivityCompat.requestPermissions(this, arrayOf(requestedPermission), requestCode)
|
||||||
}
|
}
|
||||||
snack.show()
|
snack.show()
|
||||||
val view = snack.view
|
val view = snack.view
|
||||||
val tv = view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
|
val tv = view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
|
||||||
@@ -297,16 +315,15 @@ class UserPreference : AppCompatActivity() {
|
|||||||
} else {
|
} else {
|
||||||
body()
|
body()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exports the passwords after requesting permissions
|
* Exports the passwords after requesting permissions
|
||||||
*/
|
*/
|
||||||
fun exportPasswordsWithPermissions() = runWithPermissions(
|
fun exportPasswordsWithPermissions() = runWithPermissions(
|
||||||
requestedPermission = Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
requestedPermission = Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||||
requestCode = REQUEST_EXTERNAL_STORAGE_SSH_KEY,
|
requestCode = REQUEST_EXTERNAL_STORAGE_SSH_KEY,
|
||||||
reason = "We need access to the sd-card to export the passwords"
|
reason = "We need access to the sd-card to export the passwords"
|
||||||
) {
|
) {
|
||||||
exportPasswords()
|
exportPasswords()
|
||||||
}
|
}
|
||||||
@@ -355,15 +372,16 @@ class UserPreference : AppCompatActivity() {
|
|||||||
private val isServiceEnabled: Boolean
|
private val isServiceEnabled: Boolean
|
||||||
get() {
|
get() {
|
||||||
val am = this
|
val am = this
|
||||||
.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
|
.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
|
||||||
val runningServices = am
|
val runningServices = am
|
||||||
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC)
|
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC)
|
||||||
return runningServices.any { "com.zeapo.pwdstore/.autofill.AutofillService" == it.id }
|
return runningServices.any { "com.zeapo.pwdstore/.autofill.AutofillService" == it.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int,
|
requestCode: Int, resultCode: Int,
|
||||||
data: Intent?) {
|
data: Intent?
|
||||||
|
) {
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
setResult(Activity.RESULT_CANCELED)
|
setResult(Activity.RESULT_CANCELED)
|
||||||
@@ -376,7 +394,11 @@ class UserPreference : AppCompatActivity() {
|
|||||||
val uri: Uri = data.data ?: throw IOException("Unable to open file")
|
val uri: Uri = data.data ?: throw IOException("Unable to open file")
|
||||||
|
|
||||||
copySshKey(uri)
|
copySshKey(uri)
|
||||||
Toast.makeText(this, this.resources.getString(R.string.ssh_key_success_dialog_title), Toast.LENGTH_LONG).show()
|
Toast.makeText(
|
||||||
|
this,
|
||||||
|
this.resources.getString(R.string.ssh_key_success_dialog_title),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||||
|
|
||||||
prefs.edit().putBoolean("use_generated_key", false).apply()
|
prefs.edit().putBoolean("use_generated_key", false).apply()
|
||||||
@@ -388,11 +410,13 @@ class UserPreference : AppCompatActivity() {
|
|||||||
|
|
||||||
finish()
|
finish()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
AlertDialog.Builder(this).setTitle(this.resources.getString(R.string.ssh_key_error_dialog_title)).setMessage(this.resources.getString(R.string.ssh_key_error_dialog_text) + e.message).setPositiveButton(this.resources.getString(R.string.dialog_ok)) { _, _ ->
|
AlertDialog.Builder(this)
|
||||||
// pass
|
.setTitle(this.resources.getString(R.string.ssh_key_error_dialog_title))
|
||||||
}.show()
|
.setMessage(this.resources.getString(R.string.ssh_key_error_dialog_text) + e.message)
|
||||||
|
.setPositiveButton(this.resources.getString(R.string.dialog_ok)) { _, _ ->
|
||||||
|
// pass
|
||||||
|
}.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
EDIT_GIT_INFO -> {
|
EDIT_GIT_INFO -> {
|
||||||
|
|
||||||
@@ -403,21 +427,23 @@ class UserPreference : AppCompatActivity() {
|
|||||||
if (uri?.path == Environment.getExternalStorageDirectory().path) {
|
if (uri?.path == Environment.getExternalStorageDirectory().path) {
|
||||||
// the user wants to use the root of the sdcard as a store...
|
// the user wants to use the root of the sdcard as a store...
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle("SD-Card root selected")
|
.setTitle("SD-Card root selected")
|
||||||
.setMessage("You have selected the root of your sdcard for the store. " +
|
.setMessage(
|
||||||
"This is extremely dangerous and you will lose your data " +
|
"You have selected the root of your sdcard for the store. " +
|
||||||
"as its content will, eventually, be deleted")
|
"This is extremely dangerous and you will lose your data " +
|
||||||
.setPositiveButton("Remove everything") { _, _ ->
|
"as its content will, eventually, be deleted"
|
||||||
PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
)
|
||||||
.edit()
|
.setPositiveButton("Remove everything") { _, _ ->
|
||||||
.putString("git_external_repo", uri?.path)
|
PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||||
.apply()
|
.edit()
|
||||||
}.setNegativeButton(R.string.dialog_cancel, null).show()
|
.putString("git_external_repo", uri?.path)
|
||||||
|
.apply()
|
||||||
|
}.setNegativeButton(R.string.dialog_cancel, null).show()
|
||||||
} else {
|
} else {
|
||||||
PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||||
.edit()
|
.edit()
|
||||||
.putString("git_external_repo", uri?.path)
|
.putString("git_external_repo", uri?.path)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EXPORT_PASSWORDS -> {
|
EXPORT_PASSWORDS -> {
|
||||||
@@ -433,7 +459,6 @@ class UserPreference : AppCompatActivity() {
|
|||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
Log.d("PWD_EXPORT", "Exception happened : " + e.message)
|
Log.d("PWD_EXPORT", "Exception happened : " + e.message)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
|
@@ -6,11 +6,9 @@ import android.content.Intent;
|
|||||||
import android.content.IntentSender;
|
import android.content.IntentSender;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import com.zeapo.pwdstore.PasswordStore;
|
import com.zeapo.pwdstore.PasswordStore;
|
||||||
|
|
||||||
import org.eclipse.jgit.util.StringUtils;
|
import org.eclipse.jgit.util.StringUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@@ -5,18 +5,13 @@ import android.app.Activity;
|
|||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.app.DialogFragment;
|
import android.app.DialogFragment;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView;
|
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
@@ -24,7 +19,9 @@ import android.widget.ListView;
|
|||||||
import android.widget.RadioButton;
|
import android.widget.RadioButton;
|
||||||
import android.widget.RadioGroup;
|
import android.widget.RadioGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import com.zeapo.pwdstore.PasswordStore;
|
import com.zeapo.pwdstore.PasswordStore;
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
|
|
||||||
@@ -84,12 +81,7 @@ public class AutofillFragment extends DialogFragment {
|
|||||||
((ListView) view.findViewById(R.id.matched)).setAdapter(adapter);
|
((ListView) view.findViewById(R.id.matched)).setAdapter(adapter);
|
||||||
// delete items by clicking them
|
// delete items by clicking them
|
||||||
((ListView) view.findViewById(R.id.matched)).setOnItemClickListener(
|
((ListView) view.findViewById(R.id.matched)).setOnItemClickListener(
|
||||||
new AdapterView.OnItemClickListener() {
|
(parent, view1, position, id) -> adapter.remove(adapter.getItem(position)));
|
||||||
@Override
|
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
|
||||||
adapter.remove(adapter.getItem(position));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// set the existing preference, if any
|
// set the existing preference, if any
|
||||||
SharedPreferences prefs;
|
SharedPreferences prefs;
|
||||||
@@ -116,36 +108,27 @@ public class AutofillFragment extends DialogFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add items with the + button
|
// add items with the + button
|
||||||
View.OnClickListener matchPassword = new View.OnClickListener() {
|
View.OnClickListener matchPassword = v -> {
|
||||||
@Override
|
((RadioButton) view.findViewById(R.id.match)).toggle();
|
||||||
public void onClick(View v) {
|
Intent intent = new Intent(getActivity(), PasswordStore.class);
|
||||||
((RadioButton) view.findViewById(R.id.match)).toggle();
|
intent.putExtra("matchWith", true);
|
||||||
Intent intent = new Intent(getActivity(), PasswordStore.class);
|
startActivityForResult(intent, MATCH_WITH);
|
||||||
intent.putExtra("matchWith", true);
|
|
||||||
startActivityForResult(intent, MATCH_WITH);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
view.findViewById(R.id.matchButton).setOnClickListener(matchPassword);
|
view.findViewById(R.id.matchButton).setOnClickListener(matchPassword);
|
||||||
|
|
||||||
// write to preferences when OK clicked
|
// write to preferences when OK clicked
|
||||||
builder.setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() {
|
builder.setPositiveButton(R.string.dialog_ok, (dialog, which) -> {
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
builder.setNegativeButton(R.string.dialog_cancel, null);
|
builder.setNegativeButton(R.string.dialog_cancel, null);
|
||||||
final SharedPreferences.Editor editor = prefs.edit();
|
final SharedPreferences.Editor editor = prefs.edit();
|
||||||
if (isWeb) {
|
if (isWeb) {
|
||||||
builder.setNeutralButton(R.string.autofill_apps_delete, new DialogInterface.OnClickListener() {
|
builder.setNeutralButton(R.string.autofill_apps_delete, (dialog, which) -> {
|
||||||
@Override
|
if (callingActivity.recyclerAdapter != null
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
&& packageName != null && !packageName.equals("")) {
|
||||||
if (callingActivity.recyclerAdapter != null
|
editor.remove(packageName);
|
||||||
&& packageName != null && !packageName.equals("")) {
|
callingActivity.recyclerAdapter.removeWebsite(packageName);
|
||||||
editor.remove(packageName);
|
editor.apply();
|
||||||
callingActivity.recyclerAdapter.removeWebsite(packageName);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -157,91 +140,88 @@ public class AutofillFragment extends DialogFragment {
|
|||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
AlertDialog ad = (AlertDialog) getDialog();
|
AlertDialog ad = (AlertDialog) getDialog();
|
||||||
if(ad != null) {
|
if (ad != null) {
|
||||||
Button positiveButton = ad.getButton(Dialog.BUTTON_POSITIVE);
|
Button positiveButton = ad.getButton(Dialog.BUTTON_POSITIVE);
|
||||||
positiveButton.setOnClickListener(new View.OnClickListener() {
|
positiveButton.setOnClickListener(v -> {
|
||||||
@Override
|
AutofillPreferenceActivity callingActivity = (AutofillPreferenceActivity) getActivity();
|
||||||
public void onClick(View v) {
|
Dialog dialog = getDialog();
|
||||||
AutofillPreferenceActivity callingActivity = (AutofillPreferenceActivity) getActivity();
|
|
||||||
Dialog dialog = getDialog();
|
|
||||||
|
|
||||||
SharedPreferences prefs;
|
SharedPreferences prefs;
|
||||||
if (!isWeb) {
|
if (!isWeb) {
|
||||||
prefs = getActivity().getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE);
|
prefs = getActivity().getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE);
|
||||||
} else {
|
} else {
|
||||||
prefs = getActivity().getApplicationContext().getSharedPreferences("autofill_web", Context.MODE_PRIVATE);
|
prefs = getActivity().getApplicationContext().getSharedPreferences("autofill_web", Context.MODE_PRIVATE);
|
||||||
}
|
|
||||||
SharedPreferences.Editor editor = prefs.edit();
|
|
||||||
|
|
||||||
String packageName = getArguments().getString("packageName", "");
|
|
||||||
if (isWeb) {
|
|
||||||
// handle some errors and don't dismiss the dialog
|
|
||||||
EditText webURL = (EditText) dialog.findViewById(R.id.webURL);
|
|
||||||
|
|
||||||
packageName = webURL.getText().toString();
|
|
||||||
|
|
||||||
if (packageName.equals("")) {
|
|
||||||
webURL.setError("URL cannot be blank");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String oldPackageName = getArguments().getString("packageName", "");
|
|
||||||
if (!oldPackageName.equals(packageName) && prefs.getAll().containsKey(packageName)) {
|
|
||||||
webURL.setError("URL already exists");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// write to preferences accordingly
|
|
||||||
RadioGroup radioGroup = (RadioGroup) dialog.findViewById(R.id.autofill_radiogroup);
|
|
||||||
switch (radioGroup.getCheckedRadioButtonId()) {
|
|
||||||
case R.id.use_default:
|
|
||||||
if (!isWeb) {
|
|
||||||
editor.remove(packageName);
|
|
||||||
} else {
|
|
||||||
editor.putString(packageName, "");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case R.id.first:
|
|
||||||
editor.putString(packageName, "/first");
|
|
||||||
break;
|
|
||||||
case R.id.never:
|
|
||||||
editor.putString(packageName, "/never");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
StringBuilder paths = new StringBuilder();
|
|
||||||
for (int i = 0; i < adapter.getCount(); i++) {
|
|
||||||
paths.append(adapter.getItem(i));
|
|
||||||
if (i != adapter.getCount()) {
|
|
||||||
paths.append("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
editor.putString(packageName, paths.toString());
|
|
||||||
}
|
|
||||||
editor.apply();
|
|
||||||
|
|
||||||
// notify the recycler adapter if it is loaded
|
|
||||||
if (callingActivity.recyclerAdapter != null) {
|
|
||||||
int position;
|
|
||||||
if (!isWeb) {
|
|
||||||
String appName = getArguments().getString("appName", "");
|
|
||||||
position = callingActivity.recyclerAdapter.getPosition(appName);
|
|
||||||
callingActivity.recyclerAdapter.notifyItemChanged(position);
|
|
||||||
} else {
|
|
||||||
position = callingActivity.recyclerAdapter.getPosition(packageName);
|
|
||||||
String oldPackageName = getArguments().getString("packageName", "");
|
|
||||||
if (oldPackageName.equals(packageName)) {
|
|
||||||
callingActivity.recyclerAdapter.notifyItemChanged(position);
|
|
||||||
} else if (oldPackageName.equals("")){
|
|
||||||
callingActivity.recyclerAdapter.addWebsite(packageName);
|
|
||||||
} else {
|
|
||||||
editor.remove(oldPackageName);
|
|
||||||
callingActivity.recyclerAdapter.updateWebsite(oldPackageName, packageName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dismiss();
|
|
||||||
}
|
}
|
||||||
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
|
|
||||||
|
String packageName = getArguments().getString("packageName", "");
|
||||||
|
if (isWeb) {
|
||||||
|
// handle some errors and don't dismiss the dialog
|
||||||
|
EditText webURL = dialog.findViewById(R.id.webURL);
|
||||||
|
|
||||||
|
packageName = webURL.getText().toString();
|
||||||
|
|
||||||
|
if (packageName.equals("")) {
|
||||||
|
webURL.setError("URL cannot be blank");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String oldPackageName = getArguments().getString("packageName", "");
|
||||||
|
if (!oldPackageName.equals(packageName) && prefs.getAll().containsKey(packageName)) {
|
||||||
|
webURL.setError("URL already exists");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write to preferences accordingly
|
||||||
|
RadioGroup radioGroup = dialog.findViewById(R.id.autofill_radiogroup);
|
||||||
|
switch (radioGroup.getCheckedRadioButtonId()) {
|
||||||
|
case R.id.use_default:
|
||||||
|
if (!isWeb) {
|
||||||
|
editor.remove(packageName);
|
||||||
|
} else {
|
||||||
|
editor.putString(packageName, "");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case R.id.first:
|
||||||
|
editor.putString(packageName, "/first");
|
||||||
|
break;
|
||||||
|
case R.id.never:
|
||||||
|
editor.putString(packageName, "/never");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
StringBuilder paths = new StringBuilder();
|
||||||
|
for (int i = 0; i < adapter.getCount(); i++) {
|
||||||
|
paths.append(adapter.getItem(i));
|
||||||
|
if (i != adapter.getCount()) {
|
||||||
|
paths.append("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
editor.putString(packageName, paths.toString());
|
||||||
|
}
|
||||||
|
editor.apply();
|
||||||
|
|
||||||
|
// notify the recycler adapter if it is loaded
|
||||||
|
if (callingActivity.recyclerAdapter != null) {
|
||||||
|
int position;
|
||||||
|
if (!isWeb) {
|
||||||
|
String appName = getArguments().getString("appName", "");
|
||||||
|
position = callingActivity.recyclerAdapter.getPosition(appName);
|
||||||
|
callingActivity.recyclerAdapter.notifyItemChanged(position);
|
||||||
|
} else {
|
||||||
|
position = callingActivity.recyclerAdapter.getPosition(packageName);
|
||||||
|
String oldPackageName = getArguments().getString("packageName", "");
|
||||||
|
if (oldPackageName.equals(packageName)) {
|
||||||
|
callingActivity.recyclerAdapter.notifyItemChanged(position);
|
||||||
|
} else if (oldPackageName.equals("")) {
|
||||||
|
callingActivity.recyclerAdapter.addWebsite(packageName);
|
||||||
|
} else {
|
||||||
|
editor.remove(oldPackageName);
|
||||||
|
callingActivity.recyclerAdapter.updateWebsite(oldPackageName, packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dismiss();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,19 +8,18 @@ import android.content.pm.PackageManager;
|
|||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
||||||
import androidx.core.app.NavUtils;
|
|
||||||
import androidx.core.app.TaskStackBuilder;
|
|
||||||
import androidx.core.view.MenuItemCompat;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.appcompat.widget.SearchView;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.widget.SearchView;
|
||||||
|
import androidx.core.app.NavUtils;
|
||||||
|
import androidx.core.app.TaskStackBuilder;
|
||||||
|
import androidx.core.view.MenuItemCompat;
|
||||||
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -29,9 +28,8 @@ import java.util.Map;
|
|||||||
|
|
||||||
public class AutofillPreferenceActivity extends AppCompatActivity {
|
public class AutofillPreferenceActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private RecyclerView recyclerView;
|
|
||||||
AutofillRecyclerAdapter recyclerAdapter; // let fragment have access
|
AutofillRecyclerAdapter recyclerAdapter; // let fragment have access
|
||||||
|
private RecyclerView recyclerView;
|
||||||
private PackageManager pm;
|
private PackageManager pm;
|
||||||
|
|
||||||
private boolean recreate; // flag for action on up press; origin autofill dialog? different act
|
private boolean recreate; // flag for action on up press; origin autofill dialog? different act
|
||||||
@@ -41,7 +39,7 @@ public class AutofillPreferenceActivity extends AppCompatActivity {
|
|||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
setContentView(R.layout.autofill_recycler_view);
|
setContentView(R.layout.autofill_recycler_view);
|
||||||
recyclerView = (RecyclerView) findViewById(R.id.autofill_recycler);
|
recyclerView = findViewById(R.id.autofill_recycler);
|
||||||
|
|
||||||
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
|
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
|
||||||
recyclerView.setLayoutManager(layoutManager);
|
recyclerView.setLayoutManager(layoutManager);
|
||||||
@@ -62,64 +60,8 @@ public class AutofillPreferenceActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
setTitle("Autofill Apps");
|
setTitle("Autofill Apps");
|
||||||
|
|
||||||
final FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
|
final FloatingActionButton fab = findViewById(R.id.fab);
|
||||||
fab.setOnClickListener(new View.OnClickListener() {
|
fab.setOnClickListener(v -> showDialog("", "", true));
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
showDialog("", "", true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private class populateTask extends AsyncTask<Void, Void, Void> {
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
findViewById(R.id.progress_bar).setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
|
||||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
|
||||||
List<ResolveInfo> allAppsResolveInfo = pm.queryIntentActivities(intent, 0);
|
|
||||||
List<AutofillRecyclerAdapter.AppInfo> allApps = new ArrayList<>();
|
|
||||||
|
|
||||||
for (ResolveInfo app : allAppsResolveInfo) {
|
|
||||||
allApps.add(new AutofillRecyclerAdapter.AppInfo(app.activityInfo.packageName
|
|
||||||
, app.loadLabel(pm).toString(), false, app.loadIcon(pm)));
|
|
||||||
}
|
|
||||||
|
|
||||||
SharedPreferences prefs = getSharedPreferences("autofill_web", Context.MODE_PRIVATE);
|
|
||||||
Map<String, ?> prefsMap = prefs.getAll();
|
|
||||||
for (String key : prefsMap.keySet()) {
|
|
||||||
try {
|
|
||||||
allApps.add(new AutofillRecyclerAdapter.AppInfo(key, key, true, pm.getApplicationIcon("com.android.browser")));
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
allApps.add(new AutofillRecyclerAdapter.AppInfo(key, key, true, null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
recyclerAdapter = new AutofillRecyclerAdapter(allApps, pm, AutofillPreferenceActivity.this);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void aVoid) {
|
|
||||||
runOnUiThread(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
findViewById(R.id.progress_bar).setVisibility(View.GONE);
|
|
||||||
recyclerView.setAdapter(recyclerAdapter);
|
|
||||||
Bundle extras = getIntent().getExtras();
|
|
||||||
if (extras != null) {
|
|
||||||
recyclerView.scrollToPosition(recyclerAdapter.getPosition(extras.getString("appName")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -175,4 +117,49 @@ public class AutofillPreferenceActivity extends AppCompatActivity {
|
|||||||
df.setArguments(args);
|
df.setArguments(args);
|
||||||
df.show(getFragmentManager(), "autofill_dialog");
|
df.show(getFragmentManager(), "autofill_dialog");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class populateTask extends AsyncTask<Void, Void, Void> {
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
runOnUiThread(() -> findViewById(R.id.progress_bar).setVisibility(View.VISIBLE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||||
|
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||||
|
List<ResolveInfo> allAppsResolveInfo = pm.queryIntentActivities(intent, 0);
|
||||||
|
List<AutofillRecyclerAdapter.AppInfo> allApps = new ArrayList<>();
|
||||||
|
|
||||||
|
for (ResolveInfo app : allAppsResolveInfo) {
|
||||||
|
allApps.add(new AutofillRecyclerAdapter.AppInfo(app.activityInfo.packageName
|
||||||
|
, app.loadLabel(pm).toString(), false, app.loadIcon(pm)));
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPreferences prefs = getSharedPreferences("autofill_web", Context.MODE_PRIVATE);
|
||||||
|
Map<String, ?> prefsMap = prefs.getAll();
|
||||||
|
for (String key : prefsMap.keySet()) {
|
||||||
|
try {
|
||||||
|
allApps.add(new AutofillRecyclerAdapter.AppInfo(key, key, true, pm.getApplicationIcon("com.android.browser")));
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
allApps.add(new AutofillRecyclerAdapter.AppInfo(key, key, true, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recyclerAdapter = new AutofillRecyclerAdapter(allApps, pm, AutofillPreferenceActivity.this);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void aVoid) {
|
||||||
|
runOnUiThread(() -> {
|
||||||
|
findViewById(R.id.progress_bar).setVisibility(View.GONE);
|
||||||
|
recyclerView.setAdapter(recyclerAdapter);
|
||||||
|
Bundle extras = getIntent().getExtras();
|
||||||
|
if (extras != null) {
|
||||||
|
recyclerView.scrollToPosition(recyclerAdapter.getPosition(extras.getString("appName")));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,15 +4,14 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import androidx.recyclerview.widget.SortedList;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.recyclerview.widget.SortedListAdapterCallback;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.recyclerview.widget.SortedList;
|
||||||
|
import androidx.recyclerview.widget.SortedListAdapterCallback;
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -25,50 +24,6 @@ class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecyclerAdapt
|
|||||||
private AutofillPreferenceActivity activity;
|
private AutofillPreferenceActivity activity;
|
||||||
private Drawable browserIcon = null;
|
private Drawable browserIcon = null;
|
||||||
|
|
||||||
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
|
||||||
public View view;
|
|
||||||
public TextView name;
|
|
||||||
TextView secondary;
|
|
||||||
public ImageView icon;
|
|
||||||
String packageName;
|
|
||||||
String appName;
|
|
||||||
Boolean isWeb;
|
|
||||||
|
|
||||||
ViewHolder(View view) {
|
|
||||||
super(view);
|
|
||||||
this.view = view;
|
|
||||||
name = (TextView) view.findViewById(R.id.app_name);
|
|
||||||
secondary = (TextView) view.findViewById(R.id.secondary_text);
|
|
||||||
icon = (ImageView) view.findViewById(R.id.app_icon);
|
|
||||||
view.setOnClickListener(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
activity.showDialog(packageName, appName, isWeb);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static class AppInfo {
|
|
||||||
String packageName;
|
|
||||||
String appName;
|
|
||||||
boolean isWeb;
|
|
||||||
public Drawable icon;
|
|
||||||
|
|
||||||
AppInfo(String packageName, String appName, boolean isWeb, Drawable icon) {
|
|
||||||
this.packageName = packageName;
|
|
||||||
this.appName = appName;
|
|
||||||
this.isWeb = isWeb;
|
|
||||||
this.icon = icon;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
return o != null && o instanceof AppInfo && this.appName.equals(((AppInfo) o).appName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AutofillRecyclerAdapter(List<AppInfo> allApps, final PackageManager pm
|
AutofillRecyclerAdapter(List<AppInfo> allApps, final PackageManager pm
|
||||||
, AutofillPreferenceActivity activity) {
|
, AutofillPreferenceActivity activity) {
|
||||||
SortedList.Callback<AppInfo> callback = new SortedListAdapterCallback<AppInfo>(this) {
|
SortedList.Callback<AppInfo> callback = new SortedListAdapterCallback<AppInfo>(this) {
|
||||||
@@ -76,17 +31,17 @@ class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecyclerAdapt
|
|||||||
// for the limited add/remove usage for websites
|
// for the limited add/remove usage for websites
|
||||||
@Override
|
@Override
|
||||||
public int compare(AppInfo o1, AppInfo o2) {
|
public int compare(AppInfo o1, AppInfo o2) {
|
||||||
return o1.appName.toLowerCase().compareTo(o2.appName.toLowerCase());
|
return o1.appName.toLowerCase().compareTo(o2.appName.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areContentsTheSame(AppInfo oldItem, AppInfo newItem) {
|
public boolean areContentsTheSame(AppInfo oldItem, AppInfo newItem) {
|
||||||
return oldItem.appName.equals(newItem.appName);
|
return oldItem.appName.equals(newItem.appName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean areItemsTheSame(AppInfo item1, AppInfo item2) {
|
public boolean areItemsTheSame(AppInfo item1, AppInfo item2) {
|
||||||
return item1.appName.equals(item2.appName);
|
return item1.appName.equals(item2.appName);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.apps = new SortedList<>(AppInfo.class, callback);
|
this.apps = new SortedList<>(AppInfo.class, callback);
|
||||||
@@ -170,7 +125,7 @@ class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecyclerAdapt
|
|||||||
}
|
}
|
||||||
|
|
||||||
void updateWebsite(String oldPackageName, String packageName) {
|
void updateWebsite(String oldPackageName, String packageName) {
|
||||||
apps.updateItemAt(getPosition(oldPackageName), new AppInfo (packageName, packageName, true, browserIcon));
|
apps.updateItemAt(getPosition(oldPackageName), new AppInfo(packageName, packageName, true, browserIcon));
|
||||||
allApps.remove(new AppInfo(null, oldPackageName, false, null)); // compare with equals
|
allApps.remove(new AppInfo(null, oldPackageName, false, null)); // compare with equals
|
||||||
allApps.add(new AppInfo(null, packageName, false, null));
|
allApps.add(new AppInfo(null, packageName, false, null));
|
||||||
}
|
}
|
||||||
@@ -190,4 +145,48 @@ class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecyclerAdapt
|
|||||||
}
|
}
|
||||||
apps.endBatchedUpdates();
|
apps.endBatchedUpdates();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class AppInfo {
|
||||||
|
public Drawable icon;
|
||||||
|
String packageName;
|
||||||
|
String appName;
|
||||||
|
boolean isWeb;
|
||||||
|
|
||||||
|
AppInfo(String packageName, String appName, boolean isWeb, Drawable icon) {
|
||||||
|
this.packageName = packageName;
|
||||||
|
this.appName = appName;
|
||||||
|
this.isWeb = isWeb;
|
||||||
|
this.icon = icon;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof AppInfo && this.appName.equals(((AppInfo) o).appName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||||
|
public View view;
|
||||||
|
public TextView name;
|
||||||
|
public ImageView icon;
|
||||||
|
TextView secondary;
|
||||||
|
String packageName;
|
||||||
|
String appName;
|
||||||
|
Boolean isWeb;
|
||||||
|
|
||||||
|
ViewHolder(View view) {
|
||||||
|
super(view);
|
||||||
|
this.view = view;
|
||||||
|
name = view.findViewById(R.id.app_name);
|
||||||
|
secondary = view.findViewById(R.id.secondary_text);
|
||||||
|
icon = view.findViewById(R.id.app_icon);
|
||||||
|
view.setOnClickListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
activity.showDialog(packageName, appName, isWeb);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,10 @@
|
|||||||
package com.zeapo.pwdstore.autofill;
|
package com.zeapo.pwdstore.autofill;
|
||||||
|
|
||||||
import android.accessibilityservice.AccessibilityService;
|
import android.accessibilityservice.AccessibilityService;
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
@@ -16,18 +14,16 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.accessibility.AccessibilityEvent;
|
import android.view.accessibility.AccessibilityEvent;
|
||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
import android.view.accessibility.AccessibilityWindowInfo;
|
import android.view.accessibility.AccessibilityWindowInfo;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import com.zeapo.pwdstore.PasswordEntry;
|
import com.zeapo.pwdstore.PasswordEntry;
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.openintents.openpgp.IOpenPgpService2;
|
import org.openintents.openpgp.IOpenPgpService2;
|
||||||
import org.openintents.openpgp.OpenPgpError;
|
import org.openintents.openpgp.OpenPgpError;
|
||||||
@@ -62,10 +58,6 @@ public class AutofillService extends AccessibilityService {
|
|||||||
private PasswordEntry lastPassword;
|
private PasswordEntry lastPassword;
|
||||||
private long lastPasswordMaxDate;
|
private long lastPasswordMaxDate;
|
||||||
|
|
||||||
final class Constants {
|
|
||||||
static final String TAG = "Keychain";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AutofillService getInstance() {
|
public static AutofillService getInstance() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@@ -96,11 +88,6 @@ public class AutofillService extends AccessibilityService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAccessibilityEvent(AccessibilityEvent event) {
|
public void onAccessibilityEvent(AccessibilityEvent event) {
|
||||||
// TODO there should be a better way of disabling service
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove stored password from cache
|
// remove stored password from cache
|
||||||
if (lastPassword != null && System.currentTimeMillis() > lastPasswordMaxDate) {
|
if (lastPassword != null && System.currentTimeMillis() > lastPasswordMaxDate) {
|
||||||
lastPassword = null;
|
lastPassword = null;
|
||||||
@@ -405,20 +392,14 @@ public class AutofillService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog);
|
||||||
builder.setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {
|
builder.setNegativeButton(R.string.dialog_cancel, (d, which) -> {
|
||||||
@Override
|
dialog.dismiss();
|
||||||
public void onClick(DialogInterface d, int which) {
|
dialog = null;
|
||||||
dialog.dismiss();
|
|
||||||
dialog = null;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
builder.setPositiveButton(R.string.autofill_paste, new DialogInterface.OnClickListener() {
|
builder.setPositiveButton(R.string.autofill_paste, (d, which) -> {
|
||||||
@Override
|
pasteText(node, password.getUsername());
|
||||||
public void onClick(DialogInterface d, int which) {
|
dialog.dismiss();
|
||||||
pasteText(node, password.getUsername());
|
dialog = null;
|
||||||
dialog.dismiss();
|
|
||||||
dialog = null;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
builder.setMessage(getString(R.string.autofill_paste_username, password.getUsername()));
|
builder.setMessage(getString(R.string.autofill_paste_username, password.getUsername()));
|
||||||
|
|
||||||
@@ -436,24 +417,19 @@ public class AutofillService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog);
|
||||||
builder.setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {
|
builder.setNegativeButton(R.string.dialog_cancel, (d, which) -> {
|
||||||
@Override
|
dialog.dismiss();
|
||||||
public void onClick(DialogInterface d, int which) {
|
dialog = null;
|
||||||
dialog.dismiss();
|
|
||||||
dialog = null;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
builder.setNeutralButton("Settings", new DialogInterface.OnClickListener() {
|
builder.setNeutralButton("Settings", (dialog, which) -> {
|
||||||
@Override
|
//TODO make icon? gear?
|
||||||
public void onClick(DialogInterface dialog, int which) { //TODO make icon? gear?
|
// the user will have to return to the app themselves.
|
||||||
// the user will have to return to the app themselves.
|
Intent intent = new Intent(AutofillService.this, AutofillPreferenceActivity.class);
|
||||||
Intent intent = new Intent(AutofillService.this, AutofillPreferenceActivity.class);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
intent.putExtra("packageName", packageName);
|
||||||
intent.putExtra("packageName", packageName);
|
intent.putExtra("appName", appName);
|
||||||
intent.putExtra("appName", appName);
|
intent.putExtra("isWeb", isWeb);
|
||||||
intent.putExtra("isWeb", isWeb);
|
startActivity(intent);
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// populate the dialog items, always with pick + pick and match. Could
|
// populate the dialog items, always with pick + pick and match. Could
|
||||||
@@ -464,26 +440,23 @@ public class AutofillService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
itemNames[items.size()] = getString(R.string.autofill_pick);
|
itemNames[items.size()] = getString(R.string.autofill_pick);
|
||||||
itemNames[items.size() + 1] = getString(R.string.autofill_pick_and_match);
|
itemNames[items.size() + 1] = getString(R.string.autofill_pick_and_match);
|
||||||
builder.setItems(itemNames, new DialogInterface.OnClickListener() {
|
builder.setItems(itemNames, (dialog, which) -> {
|
||||||
@Override
|
lastWhichItem = which;
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
if (which < items.size()) {
|
||||||
lastWhichItem = which;
|
bindDecryptAndVerify();
|
||||||
if (which < items.size()) {
|
} else if (which == items.size()) {
|
||||||
bindDecryptAndVerify();
|
Intent intent = new Intent(AutofillService.this, AutofillActivity.class);
|
||||||
} else if (which == items.size()) {
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
Intent intent = new Intent(AutofillService.this, AutofillActivity.class);
|
intent.putExtra("pick", true);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
startActivity(intent);
|
||||||
intent.putExtra("pick", true);
|
} else {
|
||||||
startActivity(intent);
|
lastWhichItem--; // will add one element to items, so lastWhichItem=items.size()+1
|
||||||
} else {
|
Intent intent = new Intent(AutofillService.this, AutofillActivity.class);
|
||||||
lastWhichItem--; // will add one element to items, so lastWhichItem=items.size()+1
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
Intent intent = new Intent(AutofillService.this, AutofillActivity.class);
|
intent.putExtra("pickMatchWith", true);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
intent.putExtra("packageName", packageName);
|
||||||
intent.putExtra("pickMatchWith", true);
|
intent.putExtra("isWeb", isWeb);
|
||||||
intent.putExtra("packageName", packageName);
|
startActivity(intent);
|
||||||
intent.putExtra("isWeb", isWeb);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -515,18 +488,6 @@ public class AutofillService extends AccessibilityService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class onBoundListener implements OpenPgpServiceConnection.OnBound {
|
|
||||||
@Override
|
|
||||||
public void onBound(IOpenPgpService2 service) {
|
|
||||||
decryptAndVerify();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void bindDecryptAndVerify() {
|
private void bindDecryptAndVerify() {
|
||||||
if (serviceConnection.getService() == null) {
|
if (serviceConnection.getService() == null) {
|
||||||
// the service was disconnected, need to bind again
|
// the service was disconnected, need to bind again
|
||||||
@@ -600,7 +561,6 @@ public class AutofillService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
|
|
||||||
private void pasteText(final AccessibilityNodeInfo node, final String text) {
|
private void pasteText(final AccessibilityNodeInfo node, final String text) {
|
||||||
// if the user focused on something else, take focus back
|
// if the user focused on something else, take focus back
|
||||||
// but this will open another dialog...hack to ignore this
|
// but this will open another dialog...hack to ignore this
|
||||||
@@ -627,4 +587,20 @@ public class AutofillService extends AccessibilityService {
|
|||||||
}
|
}
|
||||||
node.recycle();
|
node.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final class Constants {
|
||||||
|
static final String TAG = "Keychain";
|
||||||
|
}
|
||||||
|
|
||||||
|
private class onBoundListener implements OpenPgpServiceConnection.OnBound {
|
||||||
|
@Override
|
||||||
|
public void onBound(IOpenPgpService2 service) {
|
||||||
|
decryptAndVerify();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,9 +32,9 @@ import android.widget.Toast
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.zeapo.pwdstore.PasswordEntry
|
import com.zeapo.pwdstore.PasswordEntry
|
||||||
|
import com.zeapo.pwdstore.PasswordGeneratorDialogFragment
|
||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
import com.zeapo.pwdstore.UserPreference
|
import com.zeapo.pwdstore.UserPreference
|
||||||
import com.zeapo.pwdstore.PasswordGeneratorDialogFragment
|
|
||||||
import com.zeapo.pwdstore.utils.Otp
|
import com.zeapo.pwdstore.utils.Otp
|
||||||
import kotlinx.android.synthetic.main.decrypt_layout.*
|
import kotlinx.android.synthetic.main.decrypt_layout.*
|
||||||
import kotlinx.android.synthetic.main.encrypt_layout.*
|
import kotlinx.android.synthetic.main.encrypt_layout.*
|
||||||
@@ -73,7 +73,14 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
|
|
||||||
private val fullPath: String by lazy { intent.getStringExtra("FILE_PATH") }
|
private val fullPath: String by lazy { intent.getStringExtra("FILE_PATH") }
|
||||||
private val name: String by lazy { getName(fullPath) }
|
private val name: String by lazy { getName(fullPath) }
|
||||||
private val lastChangedString: CharSequence by lazy { getLastChangedString(intent.getIntExtra("LAST_CHANGED_TIMESTAMP", -1)) }
|
private val lastChangedString: CharSequence by lazy {
|
||||||
|
getLastChangedString(
|
||||||
|
intent.getIntExtra(
|
||||||
|
"LAST_CHANGED_TIMESTAMP",
|
||||||
|
-1
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
private val relativeParentPath: String by lazy { getParentPath(fullPath, repoPath) }
|
private val relativeParentPath: String by lazy { getParentPath(fullPath, repoPath) }
|
||||||
|
|
||||||
private val settings: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
|
private val settings: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
|
||||||
@@ -122,7 +129,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
crypto_password_category.text = getRelativePath(fullPath, repoPath)
|
crypto_password_category.text = getRelativePath(fullPath, repoPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
@@ -147,7 +153,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||||
when (item?.itemId) {
|
when (item?.itemId) {
|
||||||
android.R.id.home -> {
|
android.R.id.home -> {
|
||||||
if(passwordEntry?.hotpIsIncremented() == false) {
|
if (passwordEntry?.hotpIsIncremented() == false) {
|
||||||
setResult(RESULT_CANCELED)
|
setResult(RESULT_CANCELED)
|
||||||
}
|
}
|
||||||
finish()
|
finish()
|
||||||
@@ -157,7 +163,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
R.id.edit_password -> editPassword()
|
R.id.edit_password -> editPassword()
|
||||||
R.id.crypto_confirm_add -> encrypt()
|
R.id.crypto_confirm_add -> encrypt()
|
||||||
R.id.crypto_cancel_add -> {
|
R.id.crypto_cancel_add -> {
|
||||||
if(passwordEntry?.hotpIsIncremented() == false) {
|
if (passwordEntry?.hotpIsIncremented() == false) {
|
||||||
setResult(RESULT_CANCELED)
|
setResult(RESULT_CANCELED)
|
||||||
}
|
}
|
||||||
finish()
|
finish()
|
||||||
@@ -186,8 +192,9 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
val pi: PendingIntent = result.getParcelableExtra(RESULT_INTENT)
|
val pi: PendingIntent = result.getParcelableExtra(RESULT_INTENT)
|
||||||
try {
|
try {
|
||||||
this@PgpActivity.startIntentSenderFromChild(
|
this@PgpActivity.startIntentSenderFromChild(
|
||||||
this@PgpActivity, pi.intentSender, requestCode,
|
this@PgpActivity, pi.intentSender, requestCode,
|
||||||
null, 0, 0, 0)
|
null, 0, 0, 0
|
||||||
|
)
|
||||||
} catch (e: IntentSender.SendIntentException) {
|
} catch (e: IntentSender.SendIntentException) {
|
||||||
Log.e(TAG, "SendIntentException", e)
|
Log.e(TAG, "SendIntentException", e)
|
||||||
}
|
}
|
||||||
@@ -258,8 +265,8 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
HoldToShowPasswordTransformation(
|
HoldToShowPasswordTransformation(
|
||||||
crypto_password_toggle_show,
|
crypto_password_toggle_show,
|
||||||
Runnable { crypto_password_show.text = entry.password }
|
Runnable { crypto_password_show.text = entry.password }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,8 +284,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
crypto_copy_username.setOnClickListener { copyUsernameToClipBoard(entry.username) }
|
crypto_copy_username.setOnClickListener { copyUsernameToClipBoard(entry.username) }
|
||||||
crypto_username_show.typeface = monoTypeface
|
crypto_username_show.typeface = monoTypeface
|
||||||
crypto_username_show.text = entry.username
|
crypto_username_show.text = entry.username
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
crypto_username_show.visibility = View.GONE
|
crypto_username_show.visibility = View.GONE
|
||||||
crypto_username_show_label.visibility = View.GONE
|
crypto_username_show_label.visibility = View.GONE
|
||||||
crypto_copy_username.visibility = View.GONE
|
crypto_copy_username.visibility = View.GONE
|
||||||
@@ -295,8 +301,16 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
crypto_copy_otp.visibility = View.VISIBLE
|
crypto_copy_otp.visibility = View.VISIBLE
|
||||||
|
|
||||||
if (entry.hasTotp()) {
|
if (entry.hasTotp()) {
|
||||||
crypto_copy_otp.setOnClickListener { copyOtpToClipBoard(Otp.calculateCode(entry.totpSecret, Date().time / (1000 * Otp.TIME_WINDOW))) }
|
crypto_copy_otp.setOnClickListener {
|
||||||
crypto_otp_show.text = Otp.calculateCode(entry.totpSecret, Date().time / (1000 * Otp.TIME_WINDOW))
|
copyOtpToClipBoard(
|
||||||
|
Otp.calculateCode(
|
||||||
|
entry.totpSecret,
|
||||||
|
Date().time / (1000 * Otp.TIME_WINDOW)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
crypto_otp_show.text =
|
||||||
|
Otp.calculateCode(entry.totpSecret, Date().time / (1000 * Otp.TIME_WINDOW))
|
||||||
} else {
|
} else {
|
||||||
// we only want to calculate and show HOTP if the user requests it
|
// we only want to calculate and show HOTP if the user requests it
|
||||||
crypto_copy_otp.setOnClickListener {
|
crypto_copy_otp.setOnClickListener {
|
||||||
@@ -310,22 +324,23 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
// show a dialog asking permission to update the HOTP counter in the entry
|
// show a dialog asking permission to update the HOTP counter in the entry
|
||||||
val checkInflater = LayoutInflater.from(this)
|
val checkInflater = LayoutInflater.from(this)
|
||||||
val checkLayout = checkInflater.inflate(R.layout.otp_confirm_layout, null)
|
val checkLayout = checkInflater.inflate(R.layout.otp_confirm_layout, null)
|
||||||
val rememberCheck : CheckBox = checkLayout.findViewById(R.id.hotp_remember_checkbox)
|
val rememberCheck: CheckBox =
|
||||||
|
checkLayout.findViewById(R.id.hotp_remember_checkbox)
|
||||||
val dialogBuilder = AlertDialog.Builder(this)
|
val dialogBuilder = AlertDialog.Builder(this)
|
||||||
dialogBuilder.setView(checkLayout)
|
dialogBuilder.setView(checkLayout)
|
||||||
dialogBuilder.setMessage(R.string.dialog_update_body)
|
dialogBuilder.setMessage(R.string.dialog_update_body)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(R.string.dialog_update_positive) { _, _ ->
|
.setPositiveButton(R.string.dialog_update_positive) { _, _ ->
|
||||||
run {
|
run {
|
||||||
calculateAndCommitHotp(entry)
|
calculateAndCommitHotp(entry)
|
||||||
if (rememberCheck.isChecked) {
|
if (rememberCheck.isChecked) {
|
||||||
val editor = settings.edit()
|
val editor = settings.edit()
|
||||||
editor.putBoolean("hotp_remember_check", true)
|
editor.putBoolean("hotp_remember_check", true)
|
||||||
editor.putBoolean("hotp_remember_choice", true)
|
editor.putBoolean("hotp_remember_choice", true)
|
||||||
editor.apply()
|
editor.apply()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.setNegativeButton(R.string.dialog_update_negative) { _, _ ->
|
.setNegativeButton(R.string.dialog_update_negative) { _, _ ->
|
||||||
run {
|
run {
|
||||||
calculateHotp(entry)
|
calculateHotp(entry)
|
||||||
@@ -343,7 +358,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
crypto_otp_show.setText(R.string.hotp_pending)
|
crypto_otp_show.setText(R.string.hotp_pending)
|
||||||
}
|
}
|
||||||
crypto_otp_show.typeface = monoTypeface
|
crypto_otp_show.typeface = monoTypeface
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
crypto_otp_show.visibility = View.GONE
|
crypto_otp_show.visibility = View.GONE
|
||||||
crypto_otp_show_label.visibility = View.GONE
|
crypto_otp_show_label.visibility = View.GONE
|
||||||
@@ -353,7 +367,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
if (settings.getBoolean("copy_on_decrypt", true)) {
|
if (settings.getBoolean("copy_on_decrypt", true)) {
|
||||||
copyPasswordToClipBoard()
|
copyPasswordToClipBoard()
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "An Exception occurred", e)
|
Log.e(TAG, "An Exception occurred", e)
|
||||||
}
|
}
|
||||||
@@ -370,7 +383,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
*/
|
*/
|
||||||
private fun encrypt() {
|
private fun encrypt() {
|
||||||
// if HOTP was incremented, we leave fields as is; they have already been set
|
// if HOTP was incremented, we leave fields as is; they have already been set
|
||||||
if(intent.getStringExtra("OPERATION") != "INCREMENT") {
|
if (intent.getStringExtra("OPERATION") != "INCREMENT") {
|
||||||
editName = crypto_password_file_edit.text.toString().trim()
|
editName = crypto_password_file_edit.text.toString().trim()
|
||||||
editPass = crypto_password_edit.text.toString()
|
editPass = crypto_password_edit.text.toString()
|
||||||
editExtra = crypto_extra_edit.text.toString()
|
editExtra = crypto_extra_edit.text.toString()
|
||||||
@@ -400,37 +413,37 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
|
|
||||||
val path = if (intent.getBooleanExtra("fromDecrypt", false)) fullPath else "$fullPath/$editName.gpg"
|
val path = if (intent.getBooleanExtra("fromDecrypt", false)) fullPath else "$fullPath/$editName.gpg"
|
||||||
|
|
||||||
api?.executeApiAsync(data, iStream, oStream) { result: Intent? -> when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
api?.executeApiAsync(data, iStream, oStream) { result: Intent? ->
|
||||||
OpenPgpApi.RESULT_CODE_SUCCESS -> {
|
when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||||
try {
|
OpenPgpApi.RESULT_CODE_SUCCESS -> {
|
||||||
// TODO This might fail, we should check that the write is successful
|
try {
|
||||||
val outputStream = FileUtils.openOutputStream(File(path))
|
// TODO This might fail, we should check that the write is successful
|
||||||
outputStream.write(oStream.toByteArray())
|
val outputStream = FileUtils.openOutputStream(File(path))
|
||||||
outputStream.close()
|
outputStream.write(oStream.toByteArray())
|
||||||
|
outputStream.close()
|
||||||
|
|
||||||
val returnIntent = Intent()
|
val returnIntent = Intent()
|
||||||
returnIntent.putExtra("CREATED_FILE", path)
|
returnIntent.putExtra("CREATED_FILE", path)
|
||||||
returnIntent.putExtra("NAME", editName)
|
returnIntent.putExtra("NAME", editName)
|
||||||
returnIntent.putExtra("LONG_NAME", getLongName(fullPath, repoPath, this.editName!!))
|
returnIntent.putExtra("LONG_NAME", getLongName(fullPath, repoPath, this.editName!!))
|
||||||
|
|
||||||
// if coming from decrypt screen->edit button
|
// if coming from decrypt screen->edit button
|
||||||
if (intent.getBooleanExtra("fromDecrypt", false)) {
|
if (intent.getBooleanExtra("fromDecrypt", false)) {
|
||||||
returnIntent.putExtra("OPERATION", "EDIT")
|
returnIntent.putExtra("OPERATION", "EDIT")
|
||||||
returnIntent.putExtra("needCommit", true)
|
returnIntent.putExtra("needCommit", true)
|
||||||
|
}
|
||||||
|
setResult(RESULT_OK, returnIntent)
|
||||||
|
finish()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "An Exception occurred", e)
|
||||||
}
|
}
|
||||||
setResult(RESULT_OK, returnIntent)
|
|
||||||
finish()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "An Exception occurred", e)
|
|
||||||
}
|
}
|
||||||
|
OpenPgpApi.RESULT_CODE_ERROR -> handleError(result)
|
||||||
}
|
}
|
||||||
OpenPgpApi.RESULT_CODE_ERROR -> handleError(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens EncryptActivity with the information for this file to be edited
|
* Opens EncryptActivity with the information for this file to be edited
|
||||||
*/
|
*/
|
||||||
@@ -467,7 +480,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
private fun checkAndIncrementHotp() {
|
private fun checkAndIncrementHotp() {
|
||||||
// we do not want to increment the HOTP counter if the user has edited the entry or has not
|
// we do not want to increment the HOTP counter if the user has edited the entry or has not
|
||||||
// generated an HOTP code
|
// generated an HOTP code
|
||||||
if(intent.getStringExtra("OPERATION") != "EDIT" && passwordEntry?.hotpIsIncremented() == true) {
|
if (intent.getStringExtra("OPERATION") != "EDIT" && passwordEntry?.hotpIsIncremented() == true) {
|
||||||
editName = name.trim()
|
editName = name.trim()
|
||||||
editPass = passwordEntry?.password
|
editPass = passwordEntry?.password
|
||||||
editExtra = passwordEntry?.extraContent
|
editExtra = passwordEntry?.extraContent
|
||||||
@@ -480,13 +493,13 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calculateHotp(entry : PasswordEntry) {
|
private fun calculateHotp(entry: PasswordEntry) {
|
||||||
copyOtpToClipBoard(Otp.calculateCode(entry.hotpSecret, entry.hotpCounter + 1))
|
copyOtpToClipBoard(Otp.calculateCode(entry.hotpSecret, entry.hotpCounter + 1))
|
||||||
crypto_otp_show.text = Otp.calculateCode(entry.hotpSecret, entry.hotpCounter + 1)
|
crypto_otp_show.text = Otp.calculateCode(entry.hotpSecret, entry.hotpCounter + 1)
|
||||||
crypto_extra_show.text = entry.extraContent
|
crypto_extra_show.text = entry.extraContent
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calculateAndCommitHotp(entry : PasswordEntry) {
|
private fun calculateAndCommitHotp(entry: PasswordEntry) {
|
||||||
calculateHotp(entry)
|
calculateHotp(entry)
|
||||||
entry.incrementHotp()
|
entry.incrementHotp()
|
||||||
// we must set the result before encrypt() is called, since in
|
// we must set the result before encrypt() is called, since in
|
||||||
@@ -568,7 +581,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private inner class HoldToShowPasswordTransformation constructor(button: Button, private val onToggle: Runnable) :
|
private inner class HoldToShowPasswordTransformation constructor(button: Button, private val onToggle: Runnable) :
|
||||||
PasswordTransformationMethod(), View.OnTouchListener {
|
PasswordTransformationMethod(), View.OnTouchListener {
|
||||||
private var shown = false
|
private var shown = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -634,7 +647,12 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
sendIntent.action = Intent.ACTION_SEND
|
sendIntent.action = Intent.ACTION_SEND
|
||||||
sendIntent.putExtra(Intent.EXTRA_TEXT, passwordEntry?.password)
|
sendIntent.putExtra(Intent.EXTRA_TEXT, passwordEntry?.password)
|
||||||
sendIntent.type = "text/plain"
|
sendIntent.type = "text/plain"
|
||||||
startActivity(Intent.createChooser(sendIntent, resources.getText(R.string.send_plaintext_password_to)))//Always show a picker to give the user a chance to cancel
|
startActivity(
|
||||||
|
Intent.createChooser(
|
||||||
|
sendIntent,
|
||||||
|
resources.getText(R.string.send_plaintext_password_to)
|
||||||
|
)
|
||||||
|
)//Always show a picker to give the user a chance to cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTimer() {
|
private fun setTimer() {
|
||||||
@@ -673,7 +691,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
// This signals the DelayShow task to stop and avoids it having
|
// This signals the DelayShow task to stop and avoids it having
|
||||||
// to poll the AsyncTask.isCancelled() excessively. If skipClearing
|
// to poll the AsyncTask.isCancelled() excessively. If skipClearing
|
||||||
// is true, the cancelled task won't clear the clipboard.
|
// is true, the cancelled task won't clear the clipboard.
|
||||||
fun cancelAndSignal(skipClearing : Boolean) {
|
fun cancelAndSignal(skipClearing: Boolean) {
|
||||||
skip = skipClearing
|
skip = skipClearing
|
||||||
cancelNotify.open()
|
cancelNotify.open()
|
||||||
}
|
}
|
||||||
@@ -710,7 +728,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
while (current < showTime) {
|
while (current < showTime) {
|
||||||
|
|
||||||
// Block for 1s or until cancel is signalled
|
// Block for 1s or until cancel is signalled
|
||||||
if(cancelNotify.block(1000)) {
|
if (cancelNotify.block(1000)) {
|
||||||
Log.d("DELAY_SHOW", "Cancelled")
|
Log.d("DELAY_SHOW", "Cancelled")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -734,14 +752,17 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
val handler = Handler()
|
val handler = Handler()
|
||||||
for (i in 0..18) {
|
for (i in 0..18) {
|
||||||
val count = i.toString()
|
val count = i.toString()
|
||||||
handler.postDelayed({ clipboard.primaryClip = ClipData.newPlainText(count, count) }, (i * 500).toLong())
|
handler.postDelayed(
|
||||||
|
{ clipboard.primaryClip = ClipData.newPlainText(count, count) },
|
||||||
|
(i * 500).toLong()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (crypto_password_show != null) {
|
if (crypto_password_show != null) {
|
||||||
// clear password; if decrypt changed to encrypt layout via edit button, no need
|
// clear password; if decrypt changed to encrypt layout via edit button, no need
|
||||||
if(passwordEntry?.hotpIsIncremented() == false) {
|
if (passwordEntry?.hotpIsIncremented() == false) {
|
||||||
setResult(AppCompatActivity.RESULT_CANCELED)
|
setResult(AppCompatActivity.RESULT_CANCELED)
|
||||||
}
|
}
|
||||||
passwordEntry = null
|
passwordEntry = null
|
||||||
@@ -771,7 +792,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
|||||||
* Gets the relative path to the repository
|
* Gets the relative path to the repository
|
||||||
*/
|
*/
|
||||||
fun getRelativePath(fullPath: String, repositoryPath: String): String =
|
fun getRelativePath(fullPath: String, repositoryPath: String): String =
|
||||||
fullPath.replace(repositoryPath, "").replace("/+".toRegex(), "/")
|
fullPath.replace(repositoryPath, "").replace("/+".toRegex(), "/")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the Parent path, relative to the repository
|
* Gets the Parent path, relative to the repository
|
||||||
|
@@ -2,10 +2,7 @@ package com.zeapo.pwdstore.git;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.DialogInterface;
|
|
||||||
|
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.CloneCommand;
|
import org.eclipse.jgit.api.CloneCommand;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
|
|
||||||
@@ -79,10 +76,7 @@ public class CloneOperation extends GitOperation {
|
|||||||
+ callingActivity.getResources().getString(R.string.jgit_error_dialog_text)
|
+ callingActivity.getResources().getString(R.string.jgit_error_dialog_text)
|
||||||
+ errorMessage
|
+ errorMessage
|
||||||
+ "\nPlease check the FAQ for possible reasons why this error might occur.").
|
+ "\nPlease check the FAQ for possible reasons why this error might occur.").
|
||||||
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
|
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
}
|
|
||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,13 +2,10 @@ package com.zeapo.pwdstore.git;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -20,11 +17,11 @@ import android.widget.ArrayAdapter;
|
|||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
import com.zeapo.pwdstore.UserPreference;
|
import com.zeapo.pwdstore.UserPreference;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.RebaseCommand;
|
import org.eclipse.jgit.api.RebaseCommand;
|
||||||
@@ -39,20 +36,6 @@ import java.util.regex.Matcher;
|
|||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
public class GitActivity extends AppCompatActivity {
|
public class GitActivity extends AppCompatActivity {
|
||||||
private static final String TAG = "GitAct";
|
|
||||||
private static final String emailPattern = "^[^@]+@[^@]+$";
|
|
||||||
|
|
||||||
private Activity activity;
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
private String protocol;
|
|
||||||
private String connectionMode;
|
|
||||||
|
|
||||||
private File localDir;
|
|
||||||
private String hostname;
|
|
||||||
|
|
||||||
private SharedPreferences settings;
|
|
||||||
|
|
||||||
public static final int REQUEST_PULL = 101;
|
public static final int REQUEST_PULL = 101;
|
||||||
public static final int REQUEST_PUSH = 102;
|
public static final int REQUEST_PUSH = 102;
|
||||||
public static final int REQUEST_CLONE = 103;
|
public static final int REQUEST_CLONE = 103;
|
||||||
@@ -61,6 +44,15 @@ public class GitActivity extends AppCompatActivity {
|
|||||||
public static final int REQUEST_SYNC = 106;
|
public static final int REQUEST_SYNC = 106;
|
||||||
public static final int REQUEST_CREATE = 107;
|
public static final int REQUEST_CREATE = 107;
|
||||||
public static final int EDIT_GIT_CONFIG = 108;
|
public static final int EDIT_GIT_CONFIG = 108;
|
||||||
|
private static final String TAG = "GitAct";
|
||||||
|
private static final String emailPattern = "^[^@]+@[^@]+$";
|
||||||
|
private Activity activity;
|
||||||
|
private Context context;
|
||||||
|
private String protocol;
|
||||||
|
private String connectionMode;
|
||||||
|
private File localDir;
|
||||||
|
private String hostname;
|
||||||
|
private SharedPreferences settings;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -83,8 +75,8 @@ public class GitActivity extends AppCompatActivity {
|
|||||||
setContentView(R.layout.activity_git_clone);
|
setContentView(R.layout.activity_git_clone);
|
||||||
setTitle(R.string.title_activity_git_clone);
|
setTitle(R.string.title_activity_git_clone);
|
||||||
|
|
||||||
final Spinner protcol_spinner = (Spinner) findViewById(R.id.clone_protocol);
|
final Spinner protcol_spinner = findViewById(R.id.clone_protocol);
|
||||||
final Spinner connection_mode_spinner = (Spinner) findViewById(R.id.connection_mode);
|
final Spinner connection_mode_spinner = findViewById(R.id.connection_mode);
|
||||||
|
|
||||||
// init the spinner for connection modes
|
// init the spinner for connection modes
|
||||||
final ArrayAdapter<CharSequence> connection_mode_adapter = ArrayAdapter.createFromResource(this,
|
final ArrayAdapter<CharSequence> connection_mode_adapter = ArrayAdapter.createFromResource(this,
|
||||||
@@ -157,11 +149,11 @@ public class GitActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// init the server information
|
// init the server information
|
||||||
final EditText server_url = ((EditText) findViewById(R.id.server_url));
|
final EditText server_url = findViewById(R.id.server_url);
|
||||||
final EditText server_port = ((EditText) findViewById(R.id.server_port));
|
final EditText server_port = findViewById(R.id.server_port);
|
||||||
final EditText server_path = ((EditText) findViewById(R.id.server_path));
|
final EditText server_path = findViewById(R.id.server_path);
|
||||||
final EditText server_user = ((EditText) findViewById(R.id.server_user));
|
final EditText server_user = findViewById(R.id.server_user);
|
||||||
final EditText server_uri = ((EditText) findViewById(R.id.clone_uri));
|
final EditText server_uri = findViewById(R.id.clone_uri);
|
||||||
|
|
||||||
server_url.setText(settings.getString("git_remote_server", ""));
|
server_url.setText(settings.getString("git_remote_server", ""));
|
||||||
server_port.setText(settings.getString("git_remote_port", ""));
|
server_port.setText(settings.getString("git_remote_port", ""));
|
||||||
@@ -282,11 +274,11 @@ public class GitActivity extends AppCompatActivity {
|
|||||||
* Fills in the server_uri field with the information coming from other fields
|
* Fills in the server_uri field with the information coming from other fields
|
||||||
*/
|
*/
|
||||||
private void updateURI() {
|
private void updateURI() {
|
||||||
EditText uri = (EditText) findViewById(R.id.clone_uri);
|
EditText uri = findViewById(R.id.clone_uri);
|
||||||
EditText server_url = ((EditText) findViewById(R.id.server_url));
|
EditText server_url = findViewById(R.id.server_url);
|
||||||
EditText server_port = ((EditText) findViewById(R.id.server_port));
|
EditText server_port = findViewById(R.id.server_port);
|
||||||
EditText server_path = ((EditText) findViewById(R.id.server_path));
|
EditText server_path = findViewById(R.id.server_path);
|
||||||
EditText server_user = ((EditText) findViewById(R.id.server_user));
|
EditText server_user = findViewById(R.id.server_user);
|
||||||
|
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
switch (protocol) {
|
switch (protocol) {
|
||||||
@@ -301,7 +293,7 @@ public class GitActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
findViewById(R.id.warn_url).setVisibility(View.GONE);
|
findViewById(R.id.warn_url).setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
TextView warn_url = (TextView) findViewById(R.id.warn_url);
|
TextView warn_url = findViewById(R.id.warn_url);
|
||||||
if (!server_path.getText().toString().matches("/.*") && !server_port.getText().toString().isEmpty()) {
|
if (!server_path.getText().toString().matches("/.*") && !server_port.getText().toString().isEmpty()) {
|
||||||
warn_url.setText(R.string.warn_malformed_url_port);
|
warn_url.setText(R.string.warn_malformed_url_port);
|
||||||
warn_url.setVisibility(View.VISIBLE);
|
warn_url.setVisibility(View.VISIBLE);
|
||||||
@@ -342,11 +334,11 @@ public class GitActivity extends AppCompatActivity {
|
|||||||
* Splits the information in server_uri into the other fields
|
* Splits the information in server_uri into the other fields
|
||||||
*/
|
*/
|
||||||
private void splitURI() {
|
private void splitURI() {
|
||||||
EditText server_uri = (EditText) findViewById(R.id.clone_uri);
|
EditText server_uri = findViewById(R.id.clone_uri);
|
||||||
EditText server_url = ((EditText) findViewById(R.id.server_url));
|
EditText server_url = findViewById(R.id.server_url);
|
||||||
EditText server_port = ((EditText) findViewById(R.id.server_port));
|
EditText server_port = findViewById(R.id.server_port);
|
||||||
EditText server_path = ((EditText) findViewById(R.id.server_path));
|
EditText server_path = findViewById(R.id.server_path);
|
||||||
EditText server_user = ((EditText) findViewById(R.id.server_user));
|
EditText server_user = findViewById(R.id.server_user);
|
||||||
|
|
||||||
String uri = server_uri.getText().toString();
|
String uri = server_uri.getText().toString();
|
||||||
Pattern pattern = Pattern.compile("(.+)@([\\w\\d\\.]+):([\\d]+)*(.*)");
|
Pattern pattern = Pattern.compile("(.+)@([\\w\\d\\.]+):([\\d]+)*(.*)");
|
||||||
@@ -361,7 +353,7 @@ public class GitActivity extends AppCompatActivity {
|
|||||||
server_port.setText(matcher.group(3));
|
server_port.setText(matcher.group(3));
|
||||||
server_path.setText(matcher.group(4));
|
server_path.setText(matcher.group(4));
|
||||||
|
|
||||||
TextView warn_url = (TextView) findViewById(R.id.warn_url);
|
TextView warn_url = findViewById(R.id.warn_url);
|
||||||
if (!server_path.getText().toString().matches("/.*") && !server_port.getText().toString().isEmpty()) {
|
if (!server_path.getText().toString().matches("/.*") && !server_port.getText().toString().isEmpty()) {
|
||||||
warn_url.setText(R.string.warn_malformed_url_port);
|
warn_url.setText(R.string.warn_malformed_url_port);
|
||||||
warn_url.setVisibility(View.VISIBLE);
|
warn_url.setVisibility(View.VISIBLE);
|
||||||
@@ -467,8 +459,8 @@ public class GitActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private void showGitConfig() {
|
private void showGitConfig() {
|
||||||
// init the server information
|
// init the server information
|
||||||
final EditText git_user_name = ((EditText) findViewById(R.id.git_user_name));
|
final EditText git_user_name = findViewById(R.id.git_user_name);
|
||||||
final EditText git_user_email = ((EditText) findViewById(R.id.git_user_email));
|
final EditText git_user_email = findViewById(R.id.git_user_email);
|
||||||
|
|
||||||
git_user_name.setText(settings.getString("git_config_user_name", ""));
|
git_user_name.setText(settings.getString("git_config_user_name", ""));
|
||||||
git_user_email.setText(settings.getString("git_config_user_email", ""));
|
git_user_email.setText(settings.getString("git_config_user_email", ""));
|
||||||
@@ -476,7 +468,7 @@ public class GitActivity extends AppCompatActivity {
|
|||||||
// git status
|
// git status
|
||||||
Repository repo = PasswordRepository.getRepository(PasswordRepository.getRepositoryDirectory(activity.getApplicationContext()));
|
Repository repo = PasswordRepository.getRepository(PasswordRepository.getRepositoryDirectory(activity.getApplicationContext()));
|
||||||
if (repo != null) {
|
if (repo != null) {
|
||||||
final TextView git_commit_hash = (TextView) findViewById(R.id.git_commit_hash);
|
final TextView git_commit_hash = findViewById(R.id.git_commit_hash);
|
||||||
try {
|
try {
|
||||||
ObjectId objectId = repo.resolve(Constants.HEAD);
|
ObjectId objectId = repo.resolve(Constants.HEAD);
|
||||||
Ref ref = repo.getRef("refs/heads/master");
|
Ref ref = repo.getRef("refs/heads/master");
|
||||||
@@ -532,6 +524,7 @@ public class GitActivity extends AppCompatActivity {
|
|||||||
GitAsyncTask tasks = new GitAsyncTask(activity, false, true, this);
|
GitAsyncTask tasks = new GitAsyncTask(activity, false, true, this);
|
||||||
tasks.execute(new Git(repo).rebase().setOperation(RebaseCommand.Operation.ABORT));
|
tasks.execute(new Git(repo).rebase().setOperation(RebaseCommand.Operation.ABORT));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
showGitConfig();
|
showGitConfig();
|
||||||
@@ -560,36 +553,30 @@ public class GitActivity extends AppCompatActivity {
|
|||||||
setMessage(getResources().getString(R.string.dialog_delete_msg) + " " + localDir.toString()).
|
setMessage(getResources().getString(R.string.dialog_delete_msg) + " " + localDir.toString()).
|
||||||
setCancelable(false).
|
setCancelable(false).
|
||||||
setPositiveButton(R.string.dialog_delete,
|
setPositiveButton(R.string.dialog_delete,
|
||||||
new DialogInterface.OnClickListener() {
|
(dialog, id) -> {
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
try {
|
||||||
|
FileUtils.deleteDirectory(localDir);
|
||||||
try {
|
try {
|
||||||
FileUtils.deleteDirectory(localDir);
|
new CloneOperation(localDir, activity)
|
||||||
try {
|
.setCommand(hostname)
|
||||||
new CloneOperation(localDir, activity)
|
.executeAfterAuthentication(connectionMode, settings.getString("git_remote_username", "git"), new File(getFilesDir() + "/.ssh_key"));
|
||||||
.setCommand(hostname)
|
} catch (Exception e) {
|
||||||
.executeAfterAuthentication(connectionMode, settings.getString("git_remote_username", "git"), new File(getFilesDir() + "/.ssh_key"));
|
//This is what happens when jgit fails :(
|
||||||
} catch (Exception e) {
|
//TODO Handle the diffent cases of exceptions
|
||||||
//This is what happens when jgit fails :(
|
|
||||||
//TODO Handle the diffent cases of exceptions
|
|
||||||
e.printStackTrace();
|
|
||||||
new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
//TODO Handle the exception correctly if we are unable to delete the directory...
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
|
new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
dialog.cancel();
|
//TODO Handle the exception correctly if we are unable to delete the directory...
|
||||||
|
e.printStackTrace();
|
||||||
|
new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dialog.cancel();
|
||||||
}
|
}
|
||||||
).
|
).
|
||||||
setNegativeButton(R.string.dialog_do_not_delete,
|
setNegativeButton(R.string.dialog_do_not_delete,
|
||||||
new DialogInterface.OnClickListener() {
|
(dialog, id) -> dialog.cancel()
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
|
||||||
dialog.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
).
|
).
|
||||||
show();
|
show();
|
||||||
} else {
|
} else {
|
||||||
@@ -626,20 +613,14 @@ public class GitActivity extends AppCompatActivity {
|
|||||||
settings.getString("git_remote_location", "").isEmpty())
|
settings.getString("git_remote_location", "").isEmpty())
|
||||||
new AlertDialog.Builder(this)
|
new AlertDialog.Builder(this)
|
||||||
.setMessage(activity.getResources().getString(R.string.set_information_dialog_text))
|
.setMessage(activity.getResources().getString(R.string.set_information_dialog_text))
|
||||||
.setPositiveButton(activity.getResources().getString(R.string.dialog_positive), new DialogInterface.OnClickListener() {
|
.setPositiveButton(activity.getResources().getString(R.string.dialog_positive), (dialogInterface, i) -> {
|
||||||
@Override
|
Intent intent = new Intent(activity, UserPreference.class);
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
startActivityForResult(intent, REQUEST_PULL);
|
||||||
Intent intent = new Intent(activity, UserPreference.class);
|
|
||||||
startActivityForResult(intent, REQUEST_PULL);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.setNegativeButton(activity.getResources().getString(R.string.dialog_negative), new DialogInterface.OnClickListener() {
|
.setNegativeButton(activity.getResources().getString(R.string.dialog_negative), (dialogInterface, i) -> {
|
||||||
@Override
|
// do nothing :(
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
setResult(RESULT_OK);
|
||||||
// do nothing :(
|
finish();
|
||||||
setResult(RESULT_OK);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
|
|
||||||
|
@@ -4,10 +4,8 @@ import android.app.Activity;
|
|||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.zeapo.pwdstore.PasswordStore;
|
import com.zeapo.pwdstore.PasswordStore;
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.CommitCommand;
|
import org.eclipse.jgit.api.CommitCommand;
|
||||||
import org.eclipse.jgit.api.GitCommand;
|
import org.eclipse.jgit.api.GitCommand;
|
||||||
import org.eclipse.jgit.api.PushCommand;
|
import org.eclipse.jgit.api.PushCommand;
|
||||||
@@ -52,7 +50,7 @@ public class GitAsyncTask extends AsyncTask<GitCommand, Integer, String> {
|
|||||||
// the previous status will eventually be used to avoid a commit
|
// the previous status will eventually be used to avoid a commit
|
||||||
if (nbChanges == null || nbChanges > 0)
|
if (nbChanges == null || nbChanges > 0)
|
||||||
command.call();
|
command.call();
|
||||||
}else if (command instanceof PushCommand) {
|
} else if (command instanceof PushCommand) {
|
||||||
for (final PushResult result : ((PushCommand) command).call()) {
|
for (final PushResult result : ((PushCommand) command).call()) {
|
||||||
// Code imported (modified) from Gerrit PushOp, license Apache v2
|
// Code imported (modified) from Gerrit PushOp, license Apache v2
|
||||||
for (final RemoteRefUpdate rru : result.getRemoteUpdates()) {
|
for (final RemoteRefUpdate rru : result.getRemoteUpdates()) {
|
||||||
|
@@ -2,19 +2,17 @@ package com.zeapo.pwdstore.git;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import com.jcraft.jsch.JSch;
|
import com.jcraft.jsch.JSch;
|
||||||
import com.jcraft.jsch.JSchException;
|
import com.jcraft.jsch.JSchException;
|
||||||
import com.jcraft.jsch.KeyPair;
|
import com.jcraft.jsch.KeyPair;
|
||||||
@@ -23,7 +21,6 @@ import com.zeapo.pwdstore.UserPreference;
|
|||||||
import com.zeapo.pwdstore.git.config.GitConfigSessionFactory;
|
import com.zeapo.pwdstore.git.config.GitConfigSessionFactory;
|
||||||
import com.zeapo.pwdstore.git.config.SshConfigSessionFactory;
|
import com.zeapo.pwdstore.git.config.SshConfigSessionFactory;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.GitCommand;
|
import org.eclipse.jgit.api.GitCommand;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.transport.JschConfigSessionFactory;
|
import org.eclipse.jgit.transport.JschConfigSessionFactory;
|
||||||
@@ -109,46 +106,37 @@ public abstract class GitOperation {
|
|||||||
new AlertDialog.Builder(callingActivity)
|
new AlertDialog.Builder(callingActivity)
|
||||||
.setMessage(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_text))
|
.setMessage(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_text))
|
||||||
.setTitle(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_title))
|
.setTitle(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_title))
|
||||||
.setPositiveButton(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_import), new DialogInterface.OnClickListener() {
|
.setPositiveButton(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_import), (dialog, id) -> {
|
||||||
@Override
|
try {
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
// Ask the UserPreference to provide us with the ssh-key
|
||||||
try {
|
// onResult has to be handled by the callingActivity
|
||||||
// Ask the UserPreference to provide us with the ssh-key
|
Intent intent = new Intent(callingActivity.getApplicationContext(), UserPreference.class);
|
||||||
// onResult has to be handled by the callingActivity
|
intent.putExtra("operation", "get_ssh_key");
|
||||||
Intent intent = new Intent(callingActivity.getApplicationContext(), UserPreference.class);
|
callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE);
|
||||||
intent.putExtra("operation", "get_ssh_key");
|
} catch (Exception e) {
|
||||||
callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE);
|
System.out.println("Exception caught :(");
|
||||||
} catch (Exception e) {
|
e.printStackTrace();
|
||||||
System.out.println("Exception caught :(");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_generate), new DialogInterface.OnClickListener() {
|
.setNegativeButton(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_generate), (dialog, which) -> {
|
||||||
@Override
|
try {
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
// Duplicated code
|
||||||
try {
|
Intent intent = new Intent(callingActivity.getApplicationContext(), UserPreference.class);
|
||||||
// Duplicated code
|
intent.putExtra("operation", "make_ssh_key");
|
||||||
Intent intent = new Intent(callingActivity.getApplicationContext(), UserPreference.class);
|
callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE);
|
||||||
intent.putExtra("operation", "make_ssh_key");
|
} catch (Exception e) {
|
||||||
callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE);
|
System.out.println("Exception caught :(");
|
||||||
} catch (Exception e) {
|
e.printStackTrace();
|
||||||
System.out.println("Exception caught :(");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNeutralButton(callingActivity.getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
|
.setNeutralButton(callingActivity.getResources().getString(R.string.dialog_cancel), (dialog, id) -> {
|
||||||
@Override
|
// Finish the blank GitActivity so user doesn't have to press back
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
callingActivity.finish();
|
||||||
// Finish the blank GitActivity so user doesn't have to press back
|
|
||||||
callingActivity.finish();
|
|
||||||
}
|
|
||||||
}).show();
|
}).show();
|
||||||
} else {
|
} else {
|
||||||
LayoutInflater layoutInflater = LayoutInflater.from(callingActivity.getApplicationContext());
|
LayoutInflater layoutInflater = LayoutInflater.from(callingActivity.getApplicationContext());
|
||||||
@SuppressLint("InflateParams") final View dialogView = layoutInflater.inflate(R.layout.git_passphrase_layout, null);
|
@SuppressLint("InflateParams") final View dialogView = layoutInflater.inflate(R.layout.git_passphrase_layout, null);
|
||||||
final EditText passphrase = (EditText) dialogView.findViewById(R.id.sshkey_passphrase);
|
final EditText passphrase = dialogView.findViewById(R.id.sshkey_passphrase);
|
||||||
final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(callingActivity.getApplicationContext());
|
final SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(callingActivity.getApplicationContext());
|
||||||
final String sshKeyPassphrase = settings.getString("ssh_key_passphrase", null);
|
final String sshKeyPassphrase = settings.getString("ssh_key_passphrase", null);
|
||||||
if (showError) {
|
if (showError) {
|
||||||
@@ -172,25 +160,21 @@ public abstract class GitOperation {
|
|||||||
.setTitle(callingActivity.getResources().getString(R.string.passphrase_dialog_title))
|
.setTitle(callingActivity.getResources().getString(R.string.passphrase_dialog_title))
|
||||||
.setMessage(callingActivity.getResources().getString(R.string.passphrase_dialog_text))
|
.setMessage(callingActivity.getResources().getString(R.string.passphrase_dialog_text))
|
||||||
.setView(dialogView)
|
.setView(dialogView)
|
||||||
.setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
|
.setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialog, whichButton) -> {
|
||||||
public void onClick(DialogInterface dialog, int whichButton) {
|
if (keyPair.decrypt(passphrase.getText().toString())) {
|
||||||
if (keyPair.decrypt(passphrase.getText().toString())) {
|
boolean rememberPassphrase = ((CheckBox) dialogView.findViewById(R.id.sshkey_remember_passphrase)).isChecked();
|
||||||
boolean rememberPassphrase = ((CheckBox) dialogView.findViewById(R.id.sshkey_remember_passphrase)).isChecked();
|
if (rememberPassphrase) {
|
||||||
if (rememberPassphrase) {
|
settings.edit().putString("ssh_key_passphrase", passphrase.getText().toString()).apply();
|
||||||
settings.edit().putString("ssh_key_passphrase", passphrase.getText().toString()).apply();
|
|
||||||
}
|
|
||||||
// Authenticate using the ssh-key and then execute the command
|
|
||||||
setAuthentication(sshKey, username, passphrase.getText().toString()).execute();
|
|
||||||
} else {
|
|
||||||
settings.edit().putString("ssh_key_passphrase", null).apply();
|
|
||||||
// call back the method
|
|
||||||
executeAfterAuthentication(connectionMode, username, sshKey, true);
|
|
||||||
}
|
}
|
||||||
|
// Authenticate using the ssh-key and then execute the command
|
||||||
|
setAuthentication(sshKey, username, passphrase.getText().toString()).execute();
|
||||||
|
} else {
|
||||||
|
settings.edit().putString("ssh_key_passphrase", null).apply();
|
||||||
|
// call back the method
|
||||||
|
executeAfterAuthentication(connectionMode, username, sshKey, true);
|
||||||
}
|
}
|
||||||
}).setNegativeButton(callingActivity.getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
|
}).setNegativeButton(callingActivity.getResources().getString(R.string.dialog_cancel), (dialog, whichButton) -> {
|
||||||
public void onClick(DialogInterface dialog, int whichButton) {
|
// Do nothing.
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -200,11 +184,8 @@ public abstract class GitOperation {
|
|||||||
new AlertDialog.Builder(callingActivity)
|
new AlertDialog.Builder(callingActivity)
|
||||||
.setTitle("Unable to open the ssh-key")
|
.setTitle("Unable to open the ssh-key")
|
||||||
.setMessage("Please check that it was imported.")
|
.setMessage("Please check that it was imported.")
|
||||||
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
|
.setPositiveButton("Ok", (dialogInterface, i) -> {
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -218,16 +199,12 @@ public abstract class GitOperation {
|
|||||||
.setTitle(callingActivity.getResources().getString(R.string.passphrase_dialog_title))
|
.setTitle(callingActivity.getResources().getString(R.string.passphrase_dialog_title))
|
||||||
.setMessage(callingActivity.getResources().getString(R.string.password_dialog_text))
|
.setMessage(callingActivity.getResources().getString(R.string.password_dialog_text))
|
||||||
.setView(password)
|
.setView(password)
|
||||||
.setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
|
.setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialog, whichButton) -> {
|
||||||
public void onClick(DialogInterface dialog, int whichButton) {
|
// authenticate using the user/pwd and then execute the command
|
||||||
// authenticate using the user/pwd and then execute the command
|
setAuthentication(username, password.getText().toString()).execute();
|
||||||
setAuthentication(username, password.getText().toString()).execute();
|
|
||||||
|
|
||||||
}
|
}).setNegativeButton(callingActivity.getResources().getString(R.string.dialog_cancel), (dialog, whichButton) -> {
|
||||||
}).setNegativeButton(callingActivity.getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
|
// Do nothing.
|
||||||
public void onClick(DialogInterface dialog, int whichButton) {
|
|
||||||
// Do nothing.
|
|
||||||
}
|
|
||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,12 +216,9 @@ public abstract class GitOperation {
|
|||||||
new AlertDialog.Builder(callingActivity).
|
new AlertDialog.Builder(callingActivity).
|
||||||
setTitle(callingActivity.getResources().getString(R.string.jgit_error_dialog_title)).
|
setTitle(callingActivity.getResources().getString(R.string.jgit_error_dialog_title)).
|
||||||
setMessage(callingActivity.getResources().getString(R.string.jgit_error_dialog_text) + errorMessage).
|
setMessage(callingActivity.getResources().getString(R.string.jgit_error_dialog_text) + errorMessage).
|
||||||
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
|
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
|
||||||
@Override
|
callingActivity.setResult(Activity.RESULT_CANCELED);
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
callingActivity.finish();
|
||||||
callingActivity.setResult(Activity.RESULT_CANCELED);
|
|
||||||
callingActivity.finish();
|
|
||||||
}
|
|
||||||
}).show();
|
}).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2,13 +2,9 @@ package com.zeapo.pwdstore.git;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.DialogInterface;
|
|
||||||
|
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.PullCommand;
|
import org.eclipse.jgit.api.PullCommand;
|
||||||
import org.eclipse.jgit.merge.MergeStrategy;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
@@ -26,6 +22,7 @@ public class PullOperation extends GitOperation {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the command
|
* Sets the command
|
||||||
|
*
|
||||||
* @return the current object
|
* @return the current object
|
||||||
*/
|
*/
|
||||||
public PullOperation setCommand() {
|
public PullOperation setCommand() {
|
||||||
@@ -52,11 +49,6 @@ public class PullOperation extends GitOperation {
|
|||||||
+ callingActivity.getResources().getString(R.string.jgit_error_dialog_text)
|
+ callingActivity.getResources().getString(R.string.jgit_error_dialog_text)
|
||||||
+ errorMessage
|
+ errorMessage
|
||||||
+ "\nPlease check the FAQ for possible reasons why this error might occur.").
|
+ "\nPlease check the FAQ for possible reasons why this error might occur.").
|
||||||
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
|
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> callingActivity.finish()).show();
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
callingActivity.finish();
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,10 +2,7 @@ package com.zeapo.pwdstore.git;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.DialogInterface;
|
|
||||||
|
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.PushCommand;
|
import org.eclipse.jgit.api.PushCommand;
|
||||||
|
|
||||||
@@ -25,6 +22,7 @@ public class PushOperation extends GitOperation {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the command
|
* Sets the command
|
||||||
|
*
|
||||||
* @return the current object
|
* @return the current object
|
||||||
*/
|
*/
|
||||||
public PushOperation setCommand() {
|
public PushOperation setCommand() {
|
||||||
@@ -49,11 +47,6 @@ public class PushOperation extends GitOperation {
|
|||||||
new AlertDialog.Builder(callingActivity).
|
new AlertDialog.Builder(callingActivity).
|
||||||
setTitle(callingActivity.getResources().getString(R.string.jgit_error_dialog_title)).
|
setTitle(callingActivity.getResources().getString(R.string.jgit_error_dialog_title)).
|
||||||
setMessage(callingActivity.getString(R.string.jgit_error_push_dialog_text) + errorMessage).
|
setMessage(callingActivity.getString(R.string.jgit_error_push_dialog_text) + errorMessage).
|
||||||
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
|
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> callingActivity.finish()).show();
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
callingActivity.finish();
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,10 +2,7 @@ package com.zeapo.pwdstore.git;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.DialogInterface;
|
|
||||||
|
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.AddCommand;
|
import org.eclipse.jgit.api.AddCommand;
|
||||||
import org.eclipse.jgit.api.CommitCommand;
|
import org.eclipse.jgit.api.CommitCommand;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
@@ -64,11 +61,6 @@ public class SyncOperation extends GitOperation {
|
|||||||
+ callingActivity.getResources().getString(R.string.jgit_error_dialog_text)
|
+ callingActivity.getResources().getString(R.string.jgit_error_dialog_text)
|
||||||
+ errorMessage
|
+ errorMessage
|
||||||
+ "\nPlease check the FAQ for possible reasons why this error might occur.").
|
+ "\nPlease check the FAQ for possible reasons why this error might occur.").
|
||||||
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
|
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> callingActivity.finish()).show();
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
|
||||||
callingActivity.finish();
|
|
||||||
}
|
|
||||||
}).show();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ package com.zeapo.pwdstore.git.config;
|
|||||||
import com.jcraft.jsch.JSch;
|
import com.jcraft.jsch.JSch;
|
||||||
import com.jcraft.jsch.JSchException;
|
import com.jcraft.jsch.JSchException;
|
||||||
import com.jcraft.jsch.Session;
|
import com.jcraft.jsch.Session;
|
||||||
|
|
||||||
import org.eclipse.jgit.transport.JschConfigSessionFactory;
|
import org.eclipse.jgit.transport.JschConfigSessionFactory;
|
||||||
import org.eclipse.jgit.transport.OpenSshConfig;
|
import org.eclipse.jgit.transport.OpenSshConfig;
|
||||||
import org.eclipse.jgit.util.FS;
|
import org.eclipse.jgit.util.FS;
|
||||||
@@ -15,8 +14,7 @@ public class GitConfigSessionFactory extends JschConfigSessionFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected JSch
|
protected JSch getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException {
|
||||||
getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException {
|
|
||||||
JSch jsch = super.getJSch(hc, fs);
|
JSch jsch = super.getJSch(hc, fs);
|
||||||
jsch.removeAllIdentity();
|
jsch.removeAllIdentity();
|
||||||
return jsch;
|
return jsch;
|
||||||
|
@@ -4,7 +4,6 @@ import com.jcraft.jsch.JSch;
|
|||||||
import com.jcraft.jsch.JSchException;
|
import com.jcraft.jsch.JSchException;
|
||||||
import com.jcraft.jsch.Session;
|
import com.jcraft.jsch.Session;
|
||||||
import com.jcraft.jsch.UserInfo;
|
import com.jcraft.jsch.UserInfo;
|
||||||
|
|
||||||
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
|
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
|
||||||
import org.eclipse.jgit.transport.CredentialItem;
|
import org.eclipse.jgit.transport.CredentialItem;
|
||||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||||
|
@@ -1,310 +0,0 @@
|
|||||||
package com.zeapo.pwdstore.pwgen
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This software is provided 'as-is', without any express or implied
|
|
||||||
* warranty. In no event will Google be held liable for any damages
|
|
||||||
* arising from the use of this software.
|
|
||||||
*
|
|
||||||
* Permission is granted to anyone to use this software for any purpose,
|
|
||||||
* including commercial applications, and to alter it and redistribute it
|
|
||||||
* freely, as long as the origin is not misrepresented.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Process
|
|
||||||
import android.util.Log
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.io.DataInputStream
|
|
||||||
import java.io.DataOutputStream
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.OutputStream
|
|
||||||
import java.io.UnsupportedEncodingException
|
|
||||||
import java.security.NoSuchAlgorithmException
|
|
||||||
import java.security.Provider
|
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.security.SecureRandomSpi
|
|
||||||
import java.security.Security
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fixes for the output of the default PRNG having low entropy.
|
|
||||||
*
|
|
||||||
* The fixes need to be applied via [.apply] before any use of Java
|
|
||||||
* Cryptography Architecture primitives. A good place to invoke them is in the
|
|
||||||
* application's `onCreate`.
|
|
||||||
*/
|
|
||||||
object PRNGFixes {
|
|
||||||
|
|
||||||
private const val VERSION_CODE_JELLY_BEAN_MR2 = 18
|
|
||||||
private val BUILD_FINGERPRINT_AND_DEVICE_SERIAL = buildFingerprintAndDeviceSerial
|
|
||||||
|
|
||||||
private val buildFingerprintAndDeviceSerial: ByteArray
|
|
||||||
get() {
|
|
||||||
val result = StringBuilder()
|
|
||||||
val fingerprint = Build.FINGERPRINT
|
|
||||||
if (fingerprint != null) {
|
|
||||||
result.append(fingerprint)
|
|
||||||
}
|
|
||||||
// TODO: Build#SERIAL is deprecated and should be removed or replaced
|
|
||||||
val serial = Build.SERIAL
|
|
||||||
if (serial != null) {
|
|
||||||
result.append(serial)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return result.toString().toByteArray(charset("UTF-8"))
|
|
||||||
} catch (e: UnsupportedEncodingException) {
|
|
||||||
throw RuntimeException("UTF-8 encoding not supported")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies all fixes.
|
|
||||||
*
|
|
||||||
* @throws SecurityException if a fix is needed but could not be applied.
|
|
||||||
*/
|
|
||||||
fun apply() {
|
|
||||||
applyOpenSSLFix()
|
|
||||||
installLinuxPRNGSecureRandom()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
|
|
||||||
* fix is not needed.
|
|
||||||
*
|
|
||||||
* @throws SecurityException if the fix is needed but could not be applied.
|
|
||||||
*/
|
|
||||||
@Throws(SecurityException::class)
|
|
||||||
private fun applyOpenSSLFix() {
|
|
||||||
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
|
|
||||||
// No need to apply the fix
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Mix in the device- and invocation-specific seed.
|
|
||||||
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
|
||||||
.getMethod("RAND_seed", ByteArray::class.java)
|
|
||||||
.invoke(null, generateSeed())
|
|
||||||
|
|
||||||
// Mix output of Linux PRNG into OpenSSL's PRNG
|
|
||||||
val bytesRead = Class.forName(
|
|
||||||
"org.apache.harmony.xnet.provider.jsse.NativeCrypto"
|
|
||||||
)
|
|
||||||
.getMethod("RAND_load_file", String::class.java, Long::class.javaPrimitiveType)
|
|
||||||
.invoke(null, "/dev/urandom", 1024) as Int
|
|
||||||
if (bytesRead != 1024) {
|
|
||||||
throw IOException(
|
|
||||||
"Unexpected number of bytes read from Linux PRNG: $bytesRead"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw SecurityException("Failed to seed OpenSSL PRNG", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Installs a Linux PRNG-backed `SecureRandom` implementation as the
|
|
||||||
* default. Does nothing if the implementation is already the default or if
|
|
||||||
* there is not need to install the implementation.
|
|
||||||
*
|
|
||||||
* @throws SecurityException if the fix is needed but could not be applied.
|
|
||||||
*/
|
|
||||||
@Throws(SecurityException::class)
|
|
||||||
private fun installLinuxPRNGSecureRandom() {
|
|
||||||
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
|
|
||||||
// No need to apply the fix
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install a Linux PRNG-based SecureRandom implementation as the
|
|
||||||
// default, if not yet installed.
|
|
||||||
val secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG")
|
|
||||||
if (secureRandomProviders == null
|
|
||||||
|| secureRandomProviders.isEmpty()
|
|
||||||
|| LinuxPRNGSecureRandomProvider::class.java != secureRandomProviders[0].javaClass
|
|
||||||
) {
|
|
||||||
Security.insertProviderAt(LinuxPRNGSecureRandomProvider(), 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assert that new SecureRandom() and
|
|
||||||
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
|
|
||||||
// by the Linux PRNG-based SecureRandom implementation.
|
|
||||||
val rng1 = SecureRandom()
|
|
||||||
if (LinuxPRNGSecureRandomProvider::class.java != rng1.provider.javaClass) {
|
|
||||||
throw SecurityException(
|
|
||||||
"new SecureRandom() backed by wrong Provider: " + rng1.provider.javaClass
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val rng2: SecureRandom
|
|
||||||
try {
|
|
||||||
rng2 = SecureRandom.getInstance("SHA1PRNG")
|
|
||||||
} catch (e: NoSuchAlgorithmException) {
|
|
||||||
throw SecurityException("SHA1PRNG not available", e)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LinuxPRNGSecureRandomProvider::class.java != rng2.provider.javaClass) {
|
|
||||||
throw SecurityException(
|
|
||||||
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
|
|
||||||
+ " Provider: " + rng2.provider.javaClass
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* `Provider` of `SecureRandom` engines which pass through
|
|
||||||
* all requests to the Linux PRNG.
|
|
||||||
*/
|
|
||||||
private class LinuxPRNGSecureRandomProvider :
|
|
||||||
Provider("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses" + " /dev/urandom") {
|
|
||||||
init {
|
|
||||||
// Although /dev/urandom is not a SHA-1 PRNG, some apps
|
|
||||||
// explicitly request a SHA1PRNG SecureRandom and we thus need to
|
|
||||||
// prevent them from getting the default implementation whose output
|
|
||||||
// may have low entropy.
|
|
||||||
put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom::class.java.name)
|
|
||||||
put("SecureRandom.SHA1PRNG ImplementedIn", "Software")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [SecureRandomSpi] which passes all requests to the Linux PRNG
|
|
||||||
* (`/dev/urandom`).
|
|
||||||
*/
|
|
||||||
class LinuxPRNGSecureRandom : SecureRandomSpi() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether this engine instance has been seeded. This is needed because
|
|
||||||
* each instance needs to seed itself if the client does not explicitly
|
|
||||||
* seed it.
|
|
||||||
*/
|
|
||||||
private var mSeeded: Boolean = false
|
|
||||||
|
|
||||||
private// NOTE: Consider inserting a BufferedInputStream between
|
|
||||||
// DataInputStream and FileInputStream if you need higher
|
|
||||||
// PRNG output performance and can live with future PRNG
|
|
||||||
// output being pulled into this process prematurely.
|
|
||||||
val urandomInputStream: DataInputStream
|
|
||||||
get() = synchronized(sLock) {
|
|
||||||
if (sUrandomIn == null) {
|
|
||||||
try {
|
|
||||||
sUrandomIn = DataInputStream(
|
|
||||||
FileInputStream(URANDOM_FILE)
|
|
||||||
)
|
|
||||||
} catch (e: IOException) {
|
|
||||||
throw SecurityException(
|
|
||||||
"Failed to open "
|
|
||||||
+ URANDOM_FILE + " for reading", e
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sUrandomIn as DataInputStream
|
|
||||||
}
|
|
||||||
|
|
||||||
private val urandomOutputStream: OutputStream
|
|
||||||
@Throws(IOException::class)
|
|
||||||
get() = synchronized(sLock) {
|
|
||||||
if (sUrandomOut == null) {
|
|
||||||
sUrandomOut = FileOutputStream(URANDOM_FILE)
|
|
||||||
}
|
|
||||||
return sUrandomOut as OutputStream
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun engineSetSeed(bytes: ByteArray) {
|
|
||||||
try {
|
|
||||||
val out: OutputStream = urandomOutputStream
|
|
||||||
out.write(bytes)
|
|
||||||
out.flush()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
// On a small fraction of devices /dev/urandom is not writable.
|
|
||||||
// Log and ignore.
|
|
||||||
Log.w(
|
|
||||||
PRNGFixes::class.java.simpleName,
|
|
||||||
"Failed to mix seed into $URANDOM_FILE"
|
|
||||||
)
|
|
||||||
} finally {
|
|
||||||
mSeeded = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun engineNextBytes(bytes: ByteArray) {
|
|
||||||
if (!mSeeded) {
|
|
||||||
// Mix in the device- and invocation-specific seed.
|
|
||||||
engineSetSeed(generateSeed())
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val `in`: DataInputStream = urandomInputStream
|
|
||||||
synchronized(`in`) {
|
|
||||||
`in`.readFully(bytes)
|
|
||||||
}
|
|
||||||
} catch (e: IOException) {
|
|
||||||
throw SecurityException(
|
|
||||||
"Failed to read from $URANDOM_FILE", e
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun engineGenerateSeed(size: Int): ByteArray {
|
|
||||||
val seed = ByteArray(size)
|
|
||||||
engineNextBytes(seed)
|
|
||||||
return seed
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
|
|
||||||
* are passed through to the Linux PRNG (/dev/urandom). Instances of
|
|
||||||
* this class seed themselves by mixing in the current time, PID, UID,
|
|
||||||
* build fingerprint, and hardware serial number (where available) into
|
|
||||||
* Linux PRNG.
|
|
||||||
*
|
|
||||||
* Concurrency: Read requests to the underlying Linux PRNG are
|
|
||||||
* serialized (on sLock) to ensure that multiple threads do not get
|
|
||||||
* duplicated PRNG output.
|
|
||||||
*/
|
|
||||||
|
|
||||||
private val URANDOM_FILE = File("/dev/urandom")
|
|
||||||
|
|
||||||
private val sLock = Any()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Input stream for reading from Linux PRNG or `null` if not yet
|
|
||||||
* opened.
|
|
||||||
*/
|
|
||||||
private var sUrandomIn: DataInputStream? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Output stream for writing to Linux PRNG or `null` if not yet
|
|
||||||
* opened.
|
|
||||||
*/
|
|
||||||
private var sUrandomOut: OutputStream? = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a device- and invocation-specific seed to be mixed into the
|
|
||||||
* Linux PRNG.
|
|
||||||
*/
|
|
||||||
private fun generateSeed(): ByteArray {
|
|
||||||
try {
|
|
||||||
val seedBuffer = ByteArrayOutputStream()
|
|
||||||
val seedBufferOut = DataOutputStream(seedBuffer)
|
|
||||||
seedBufferOut.writeLong(System.currentTimeMillis())
|
|
||||||
seedBufferOut.writeLong(System.nanoTime())
|
|
||||||
seedBufferOut.writeInt(Process.myPid())
|
|
||||||
seedBufferOut.writeInt(Process.myUid())
|
|
||||||
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL)
|
|
||||||
seedBufferOut.close()
|
|
||||||
return seedBuffer.toByteArray()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
throw SecurityException("Failed to generate seed", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,26 +1,25 @@
|
|||||||
package com.zeapo.pwdstore.pwgen
|
package com.zeapo.pwdstore.pwgen
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
|
||||||
|
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
|
||||||
object PasswordGenerator {
|
object PasswordGenerator {
|
||||||
internal val DIGITS = 0x0001
|
internal const val DIGITS = 0x0001
|
||||||
internal val UPPERS = 0x0002
|
internal const val UPPERS = 0x0002
|
||||||
internal val SYMBOLS = 0x0004
|
internal const val SYMBOLS = 0x0004
|
||||||
internal val AMBIGUOUS = 0x0008
|
internal const val AMBIGUOUS = 0x0008
|
||||||
internal val NO_VOWELS = 0x0010
|
internal const val NO_VOWELS = 0x0010
|
||||||
|
|
||||||
internal val DIGITS_STR = "0123456789"
|
internal const val DIGITS_STR = "0123456789"
|
||||||
internal val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
internal const val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
internal val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
|
internal const val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
|
||||||
internal val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
internal const val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
|
||||||
internal val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
|
internal const val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
|
||||||
internal val VOWELS_STR = "01aeiouyAEIOUY"
|
internal const val VOWELS_STR = "01aeiouyAEIOUY"
|
||||||
|
|
||||||
// No a, c, n, h, H, C, 1, N
|
// No a, c, n, h, H, C, 1, N
|
||||||
private val pwOptions = "0ABsvy"
|
private const val pwOptions = "0ABsvy"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets password generation preferences.
|
* Sets password generation preferences.
|
||||||
|
@@ -3,15 +3,14 @@ package com.zeapo.pwdstore.utils;
|
|||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -19,9 +18,9 @@ import java.util.Set;
|
|||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
public abstract class EntryRecyclerAdapter extends RecyclerView.Adapter<EntryRecyclerAdapter.ViewHolder> {
|
public abstract class EntryRecyclerAdapter extends RecyclerView.Adapter<EntryRecyclerAdapter.ViewHolder> {
|
||||||
|
final Set<Integer> selectedItems = new TreeSet<>();
|
||||||
private final Activity activity;
|
private final Activity activity;
|
||||||
private final ArrayList<PasswordItem> values;
|
private final ArrayList<PasswordItem> values;
|
||||||
final Set<Integer> selectedItems = new TreeSet<>();
|
|
||||||
|
|
||||||
EntryRecyclerAdapter(Activity activity, ArrayList<PasswordItem> values) {
|
EntryRecyclerAdapter(Activity activity, ArrayList<PasswordItem> values) {
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
@@ -85,18 +84,13 @@ public abstract class EntryRecyclerAdapter extends RecyclerView.Adapter<EntryRec
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
View.OnLongClickListener getOnLongClickListener(ViewHolder holder, PasswordItem pass) {
|
View.OnLongClickListener getOnLongClickListener(ViewHolder holder, PasswordItem pass) {
|
||||||
return new View.OnLongClickListener() {
|
return v -> false;
|
||||||
@Override
|
|
||||||
public boolean onLongClick(View v) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the contents of a view (invoked by the layout manager)
|
// Replace the contents of a view (invoked by the layout manager)
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
|
||||||
final PasswordItem pass = getValues().get(position);
|
final PasswordItem pass = getValues().get(position);
|
||||||
holder.name.setText(pass.toString());
|
holder.name.setText(pass.toString());
|
||||||
if (pass.getType() == PasswordItem.TYPE_CATEGORY) {
|
if (pass.getType() == PasswordItem.TYPE_CATEGORY) {
|
||||||
@@ -127,6 +121,17 @@ public abstract class EntryRecyclerAdapter extends RecyclerView.Adapter<EntryRec
|
|||||||
@NonNull
|
@NonNull
|
||||||
protected abstract View.OnClickListener getOnClickListener(ViewHolder holder, PasswordItem pass);
|
protected abstract View.OnClickListener getOnClickListener(ViewHolder holder, PasswordItem pass);
|
||||||
|
|
||||||
|
// Create new views (invoked by the layout manager)
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public PasswordRecyclerAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
|
||||||
|
int viewType) {
|
||||||
|
// create a new view
|
||||||
|
View v = LayoutInflater.from(parent.getContext())
|
||||||
|
.inflate(R.layout.password_row_layout, parent, false);
|
||||||
|
return new ViewHolder(v);
|
||||||
|
}
|
||||||
|
|
||||||
// Provide a reference to the views for each data item
|
// Provide a reference to the views for each data item
|
||||||
// Complex data items may need more than one view per item, and
|
// Complex data items may need more than one view per item, and
|
||||||
// you provide access to all the views for a data item in a view holder
|
// you provide access to all the views for a data item in a view holder
|
||||||
@@ -140,19 +145,9 @@ public abstract class EntryRecyclerAdapter extends RecyclerView.Adapter<EntryRec
|
|||||||
ViewHolder(View v) {
|
ViewHolder(View v) {
|
||||||
super(v);
|
super(v);
|
||||||
view = v;
|
view = v;
|
||||||
name = (TextView) view.findViewById(R.id.label);
|
name = view.findViewById(R.id.label);
|
||||||
type = (TextView) view.findViewById(R.id.type);
|
type = view.findViewById(R.id.type);
|
||||||
typeImage = (ImageView) view.findViewById(R.id.type_image);
|
typeImage = view.findViewById(R.id.type_image);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new views (invoked by the layout manager)
|
|
||||||
@Override
|
|
||||||
public PasswordRecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
|
|
||||||
int viewType) {
|
|
||||||
// create a new view
|
|
||||||
View v = LayoutInflater.from(parent.getContext())
|
|
||||||
.inflate(R.layout.password_row_layout, parent, false);
|
|
||||||
return new ViewHolder(v);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
package com.zeapo.pwdstore.utils;
|
package com.zeapo.pwdstore.utils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import com.zeapo.pwdstore.SelectFolderActivity;
|
import com.zeapo.pwdstore.SelectFolderActivity;
|
||||||
import com.zeapo.pwdstore.SelectFolderFragment;
|
import com.zeapo.pwdstore.SelectFolderFragment;
|
||||||
|
|
||||||
@@ -19,12 +18,9 @@ public class FolderRecyclerAdapter extends EntryRecyclerAdapter {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
protected View.OnClickListener getOnClickListener(final ViewHolder holder, final PasswordItem pass) {
|
protected View.OnClickListener getOnClickListener(final ViewHolder holder, final PasswordItem pass) {
|
||||||
return new View.OnClickListener() {
|
return v -> {
|
||||||
@Override
|
listener.onFragmentInteraction(pass);
|
||||||
public void onClick(View v) {
|
notifyItemChanged(holder.getAdapterPosition());
|
||||||
listener.onFragmentInteraction(pass);
|
|
||||||
notifyItemChanged(holder.getAdapterPosition());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,18 +1,16 @@
|
|||||||
package com.zeapo.pwdstore.utils;
|
package com.zeapo.pwdstore.utils;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Base32;
|
import org.apache.commons.codec.binary.Base32;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
public class Otp {
|
public class Otp {
|
||||||
public static final int TIME_WINDOW = 30;
|
public static final int TIME_WINDOW = 30;
|
||||||
|
|
||||||
@@ -27,7 +25,7 @@ public class Otp {
|
|||||||
public static String calculateCode(String secret, long counter) {
|
public static String calculateCode(String secret, long counter) {
|
||||||
SecretKeySpec signingKey = new SecretKeySpec(BASE_32.decode(secret), ALGORITHM);
|
SecretKeySpec signingKey = new SecretKeySpec(BASE_32.decode(secret), ALGORITHM);
|
||||||
|
|
||||||
Mac mac = null;
|
Mac mac;
|
||||||
try {
|
try {
|
||||||
mac = Mac.getInstance(ALGORITHM);
|
mac = Mac.getInstance(ALGORITHM);
|
||||||
mac.init(signingKey);
|
mac.init(signingKey);
|
||||||
|
@@ -1,8 +1,7 @@
|
|||||||
package com.zeapo.pwdstore.utils;
|
package com.zeapo.pwdstore.utils;
|
||||||
|
|
||||||
import com.zeapo.pwdstore.crypto.PgpActivity;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import com.zeapo.pwdstore.crypto.PgpActivity;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
@@ -18,8 +17,9 @@ public class PasswordItem implements Comparable {
|
|||||||
private final String fullPathToParent;
|
private final String fullPathToParent;
|
||||||
private final String longName;
|
private final String longName;
|
||||||
|
|
||||||
/** Create a password item
|
/**
|
||||||
*
|
* Create a password item
|
||||||
|
* <p>
|
||||||
* Make it protected so that we use a builder
|
* Make it protected so that we use a builder
|
||||||
*/
|
*/
|
||||||
private PasswordItem(String name, PasswordItem parent, char type, File file, File rootDir) {
|
private PasswordItem(String name, PasswordItem parent, char type, File file, File rootDir) {
|
||||||
@@ -33,35 +33,39 @@ public class PasswordItem implements Comparable {
|
|||||||
longName = PgpActivity.getLongName(fullPathToParent, rootDir.getAbsolutePath(), toString());
|
longName = PgpActivity.getLongName(fullPathToParent, rootDir.getAbsolutePath(), toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a new Category item
|
/**
|
||||||
|
* Create a new Category item
|
||||||
*/
|
*/
|
||||||
public static PasswordItem newCategory(String name, File file, PasswordItem parent, File rootDir) {
|
public static PasswordItem newCategory(String name, File file, PasswordItem parent, File rootDir) {
|
||||||
return new PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir);
|
return new PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a new parentless category item
|
/**
|
||||||
|
* Create a new parentless category item
|
||||||
*/
|
*/
|
||||||
public static PasswordItem newCategory(String name, File file, File rootDir) {
|
public static PasswordItem newCategory(String name, File file, File rootDir) {
|
||||||
return new PasswordItem(name, null, TYPE_CATEGORY, file, rootDir);
|
return new PasswordItem(name, null, TYPE_CATEGORY, file, rootDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a new password item
|
/**
|
||||||
|
* Create a new password item
|
||||||
*/
|
*/
|
||||||
public static PasswordItem newPassword(String name, File file, PasswordItem parent, File rootDir) {
|
public static PasswordItem newPassword(String name, File file, PasswordItem parent, File rootDir) {
|
||||||
return new PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir);
|
return new PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create a new parentless password item
|
/**
|
||||||
|
* Create a new parentless password item
|
||||||
*/
|
*/
|
||||||
public static PasswordItem newPassword(String name, File file, File rootDir) {
|
public static PasswordItem newPassword(String name, File file, File rootDir) {
|
||||||
return new PasswordItem(name, null, TYPE_PASSWORD, file, rootDir);
|
return new PasswordItem(name, null, TYPE_PASSWORD, file, rootDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public char getType(){
|
public char getType() {
|
||||||
return this.type;
|
return this.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getName(){
|
String getName() {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,8 +85,9 @@ public class PasswordItem implements Comparable {
|
|||||||
return longName;
|
return longName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString() {
|
||||||
return this.getName().replace(".gpg", "");
|
return this.getName().replace(".gpg", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
package com.zeapo.pwdstore.utils;
|
package com.zeapo.pwdstore.utils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.appcompat.view.ActionMode;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.view.ActionMode;
|
||||||
import com.zeapo.pwdstore.PasswordFragment;
|
import com.zeapo.pwdstore.PasswordFragment;
|
||||||
import com.zeapo.pwdstore.PasswordStore;
|
import com.zeapo.pwdstore.PasswordStore;
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
@@ -19,63 +18,6 @@ public class PasswordRecyclerAdapter extends EntryRecyclerAdapter {
|
|||||||
private final PasswordFragment.OnFragmentInteractionListener listener;
|
private final PasswordFragment.OnFragmentInteractionListener listener;
|
||||||
public ActionMode mActionMode;
|
public ActionMode mActionMode;
|
||||||
private Boolean canEdit;
|
private Boolean canEdit;
|
||||||
|
|
||||||
// Provide a suitable constructor (depends on the kind of dataset)
|
|
||||||
public PasswordRecyclerAdapter(PasswordStore activity, PasswordFragment.OnFragmentInteractionListener listener, ArrayList<PasswordItem> values) {
|
|
||||||
super(activity, values);
|
|
||||||
this.activity = activity;
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
protected View.OnLongClickListener getOnLongClickListener(final ViewHolder holder, final PasswordItem pass) {
|
|
||||||
return new View.OnLongClickListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onLongClick(View v) {
|
|
||||||
if (mActionMode != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
toggleSelection(holder.getAdapterPosition());
|
|
||||||
canEdit = pass.getType() == PasswordItem.TYPE_PASSWORD;
|
|
||||||
// Start the CAB using the ActionMode.Callback
|
|
||||||
mActionMode = activity.startSupportActionMode(mActionModeCallback);
|
|
||||||
mActionMode.setTitle("" + selectedItems.size());
|
|
||||||
mActionMode.invalidate();
|
|
||||||
notifyItemChanged(holder.getAdapterPosition());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
protected View.OnClickListener getOnClickListener(final ViewHolder holder, final PasswordItem pass) {
|
|
||||||
return new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
if (mActionMode != null) {
|
|
||||||
toggleSelection(holder.getAdapterPosition());
|
|
||||||
mActionMode.setTitle("" + selectedItems.size());
|
|
||||||
if (selectedItems.isEmpty()) {
|
|
||||||
mActionMode.finish();
|
|
||||||
} else if (selectedItems.size() == 1 && !canEdit) {
|
|
||||||
if (getValues().get(selectedItems.iterator().next()).getType() == PasswordItem.TYPE_PASSWORD) {
|
|
||||||
canEdit = true;
|
|
||||||
mActionMode.invalidate();
|
|
||||||
}
|
|
||||||
} else if (selectedItems.size() >= 1 && canEdit) {
|
|
||||||
canEdit = false;
|
|
||||||
mActionMode.invalidate();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
listener.onFragmentInteraction(pass);
|
|
||||||
}
|
|
||||||
notifyItemChanged(holder.getAdapterPosition());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
|
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
|
||||||
|
|
||||||
// Called when the action mode is created; startActionMode() was called
|
// Called when the action mode is created; startActionMode() was called
|
||||||
@@ -136,4 +78,54 @@ public class PasswordRecyclerAdapter extends EntryRecyclerAdapter {
|
|||||||
activity.findViewById(R.id.fab).setVisibility(View.VISIBLE);
|
activity.findViewById(R.id.fab).setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Provide a suitable constructor (depends on the kind of dataset)
|
||||||
|
public PasswordRecyclerAdapter(PasswordStore activity, PasswordFragment.OnFragmentInteractionListener listener, ArrayList<PasswordItem> values) {
|
||||||
|
super(activity, values);
|
||||||
|
this.activity = activity;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
protected View.OnLongClickListener getOnLongClickListener(final ViewHolder holder, final PasswordItem pass) {
|
||||||
|
return v -> {
|
||||||
|
if (mActionMode != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
toggleSelection(holder.getAdapterPosition());
|
||||||
|
canEdit = pass.getType() == PasswordItem.TYPE_PASSWORD;
|
||||||
|
// Start the CAB using the ActionMode.Callback
|
||||||
|
mActionMode = activity.startSupportActionMode(mActionModeCallback);
|
||||||
|
mActionMode.setTitle("" + selectedItems.size());
|
||||||
|
mActionMode.invalidate();
|
||||||
|
notifyItemChanged(holder.getAdapterPosition());
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
protected View.OnClickListener getOnClickListener(final ViewHolder holder, final PasswordItem pass) {
|
||||||
|
return v -> {
|
||||||
|
if (mActionMode != null) {
|
||||||
|
toggleSelection(holder.getAdapterPosition());
|
||||||
|
mActionMode.setTitle("" + selectedItems.size());
|
||||||
|
if (selectedItems.isEmpty()) {
|
||||||
|
mActionMode.finish();
|
||||||
|
} else if (selectedItems.size() == 1 && !canEdit) {
|
||||||
|
if (getValues().get(selectedItems.iterator().next()).getType() == PasswordItem.TYPE_PASSWORD) {
|
||||||
|
canEdit = true;
|
||||||
|
mActionMode.invalidate();
|
||||||
|
}
|
||||||
|
} else if (selectedItems.size() >= 1 && canEdit) {
|
||||||
|
canEdit = false;
|
||||||
|
mActionMode.invalidate();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
listener.onFragmentInteraction(pass);
|
||||||
|
}
|
||||||
|
notifyItemChanged(holder.getAdapterPosition());
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,6 @@ import android.content.Context;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.apache.commons.io.filefilter.FileFilterUtils;
|
import org.apache.commons.io.filefilter.FileFilterUtils;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
@@ -269,9 +268,7 @@ public class PasswordRepository {
|
|||||||
return (p2.getType() + p1.getName())
|
return (p2.getType() + p1.getName())
|
||||||
.compareToIgnoreCase(p1.getType() + p2.getName());
|
.compareToIgnoreCase(p1.getType() + p2.getName());
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
;
|
|
||||||
|
|
||||||
private Comparator<PasswordItem> comparator;
|
private Comparator<PasswordItem> comparator;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user