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:
Harsh Shandilya 2019-01-02 22:44:52 +05:30 committed by حسين
parent d5e589d7e4
commit c200566bca
34 changed files with 923 additions and 1426 deletions

View file

@ -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()

View file

@ -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();

View file

@ -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 {
@ -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()) {

View file

@ -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) {
@ -64,14 +60,14 @@ public class PasswordFragment extends Fragment{
} }
@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,8 +87,7 @@ 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() ?
@ -119,7 +109,6 @@ public class PasswordFragment extends Fragment{
((PasswordStore) getActivity()).decryptPassword(item); ((PasswordStore) getActivity()).decryptPassword(item);
} }
} }
}
}; };
} catch (ClassCastException e) { } catch (ClassCastException e) {
throw new ClassCastException(context.toString() throw new ClassCastException(context.toString()
@ -152,6 +141,7 @@ public class PasswordFragment extends Fragment{
/** /**
* 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,6 +156,7 @@ 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
*/ */
@ -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);
}
} }

View file

@ -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
public void onClick(DialogInterface dialog, int which) {
EditText edit = callingActivity.findViewById(R.id.crypto_password_edit); EditText edit = callingActivity.findViewById(R.id.crypto_password_edit);
TextView generate = view.findViewById(R.id.passwordText); edit.setText(passwordText.getText());
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
public void onShow(DialogInterface dialog) {
setPreferences(); setPreferences();
TextView textView = view.findViewById(R.id.passwordText); passwordText.setText(PasswordGenerator.INSTANCE.generate(getActivity().getApplicationContext()).get(0));
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
public void onClick(View v) {
setPreferences(); setPreferences();
TextView textView = view.findViewById(R.id.passwordText); passwordText.setText(PasswordGenerator.INSTANCE.generate(callingActivity.getApplicationContext()).get(0));
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);
} }
} }

View file

@ -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
public void onClick(View view) {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_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
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(activity, UserPreference.class); Intent intent = new Intent(activity, UserPreference.class);
startActivityForResult(intent, GitActivity.REQUEST_INIT); 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
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(activity, UserPreference.class); Intent intent = new Intent(activity, UserPreference.class);
startActivity(intent); startActivity(intent);
}
}).show(); }).show();
return; return;
} }
@ -559,9 +539,7 @@ 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
public void onClick(DialogInterface dialogInterface, int i) {
item.getFile().delete(); item.getFile().delete();
adapter.remove(position); adapter.remove(position);
it.remove(); it.remove();
@ -570,14 +548,10 @@ public class PasswordStore extends AppCompatActivity {
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
public void onClick(DialogInterface dialogInterface, int i) {
it.remove(); it.remove();
deletePasswords(adapter, selectedItems); deletePasswords(adapter, selectedItems);
}
}) })
.show(); .show();
} }
@ -771,8 +745,7 @@ 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) {
@ -787,10 +760,8 @@ public class PasswordStore extends AppCompatActivity {
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);
@ -803,9 +774,7 @@ public class PasswordStore extends AppCompatActivity {
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
public void onClick(DialogInterface dialog, int which) {
switch (operation) { switch (operation) {
case NEW_REPO_BUTTON: case NEW_REPO_BUTTON:
initializeRepositoryInfo(); initializeRepositoryInfo();
@ -818,18 +787,13 @@ public class PasswordStore extends AppCompatActivity {
startActivityForResult(intent, GitActivity.REQUEST_CLONE); startActivityForResult(intent, GitActivity.REQUEST_CLONE);
break; break;
} }
}
}) })
.setNegativeButton(getResources().getString(R.string.change), new DialogInterface.OnClickListener() { .setNegativeButton(getResources().getString(R.string.change), (dialog12, which) -> {
@Override
public void onClick(DialogInterface dialog, int which) {
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);
}
}).show(); }).show();
} }
}
}) })
.show(); .show();
} }

View file

@ -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

View file

@ -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) {
@ -57,7 +51,7 @@ public class SelectFolderFragment extends Fragment{
} }
@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,8 +75,7 @@ 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());
@ -93,7 +86,6 @@ public class SelectFolderFragment extends Fragment{
((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()
@ -103,6 +95,7 @@ public class SelectFolderFragment extends Fragment{
/** /**
* 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);
}
} }

View file

@ -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,9 +85,7 @@ 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
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
EditText editText = v.findViewById(R.id.passphrase); EditText editText = v.findViewById(R.id.passphrase);
int selection = editText.getSelectionEnd(); int selection = editText.getSelectionEnd();
if (isChecked) { if (isChecked) {
@ -72,7 +94,6 @@ public class SshKeyGen extends AppCompatActivity {
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); 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
public void onClick(DialogInterface dialog, int which) {
if (getActivity() instanceof SshKeyGen) if (getActivity() instanceof SshKeyGen)
getActivity().finish(); 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
public void onShow(DialogInterface dialog) {
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL); Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
b.setOnClickListener(new View.OnClickListener() { b.setOnClickListener(v1 -> {
@Override TextView textView1 = getDialog().findViewById(R.id.public_key);
public void onClick(View v) {
TextView textView = getDialog().findViewById(R.id.public_key);
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE); ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("public key", textView.getText().toString()); ClipData clip = ClipData.newPlainText("public key", textView1.getText().toString());
clipboard.setPrimaryClip(clip); 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
public void onClick(DialogInterface dialogInterface, int i) {
// pass // 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);
}
} }

View file

@ -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)
} }
} }

View file

@ -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,13 +76,15 @@ class UserPreference : AppCompatActivity() {
true true
} }
findPreference("ssh_key_clear_passphrase").onPreferenceClickListener = Preference.OnPreferenceClickListener { findPreference("ssh_key_clear_passphrase").onPreferenceClickListener =
Preference.OnPreferenceClickListener {
sharedPreferences.edit().putString("ssh_key_passphrase", null).apply() sharedPreferences.edit().putString("ssh_key_passphrase", null).apply()
it.isEnabled = false it.isEnabled = false
true true
} }
findPreference("hotp_remember_clear_choice").onPreferenceClickListener = Preference.OnPreferenceClickListener { findPreference("hotp_remember_clear_choice").onPreferenceClickListener =
Preference.OnPreferenceClickListener {
sharedPreferences.edit().putBoolean("hotp_remember_check", false).apply() sharedPreferences.edit().putBoolean("hotp_remember_check", false).apply()
it.isEnabled = false it.isEnabled = false
true true
@ -118,14 +121,16 @@ class UserPreference : AppCompatActivity() {
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() } } }
.setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ -> run { dialogInterface.cancel() } }
.show() .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
} }
} }
@ -220,7 +239,6 @@ class UserPreference : AppCompatActivity() {
startActivityForResult(i, SELECT_GIT_DIRECTORY) startActivityForResult(i, SELECT_GIT_DIRECTORY)
}.setNegativeButton(R.string.dialog_cancel, null).show() }.setNegativeButton(R.string.dialog_cancel, null).show()
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -297,7 +315,6 @@ class UserPreference : AppCompatActivity() {
} else { } else {
body() body()
} }
} }
/** /**
@ -361,9 +378,10 @@ class UserPreference : AppCompatActivity() {
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)
.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)) { _, _ ->
// pass // pass
}.show() }.show()
} }
} }
EDIT_GIT_INFO -> { EDIT_GIT_INFO -> {
@ -404,9 +428,11 @@ class UserPreference : AppCompatActivity() {
// 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(
"You have selected the root of your sdcard for the store. " +
"This is extremely dangerous and you will lose your data " + "This is extremely dangerous and you will lose your data " +
"as its content will, eventually, be deleted") "as its content will, eventually, be deleted"
)
.setPositiveButton("Remove everything") { _, _ -> .setPositiveButton("Remove everything") { _, _ ->
PreferenceManager.getDefaultSharedPreferences(applicationContext) PreferenceManager.getDefaultSharedPreferences(applicationContext)
.edit() .edit()
@ -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 -> {

View file

@ -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;

View file

@ -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,37 +108,28 @@ 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
public void onClick(View v) {
((RadioButton) view.findViewById(R.id.match)).toggle(); ((RadioButton) view.findViewById(R.id.match)).toggle();
Intent intent = new Intent(getActivity(), PasswordStore.class); Intent intent = new Intent(getActivity(), PasswordStore.class);
intent.putExtra("matchWith", true); intent.putExtra("matchWith", true);
startActivityForResult(intent, MATCH_WITH); 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
public void onClick(DialogInterface dialog, int which) {
if (callingActivity.recyclerAdapter != null if (callingActivity.recyclerAdapter != null
&& packageName != null && !packageName.equals("")) { && packageName != null && !packageName.equals("")) {
editor.remove(packageName); editor.remove(packageName);
callingActivity.recyclerAdapter.removeWebsite(packageName); callingActivity.recyclerAdapter.removeWebsite(packageName);
editor.apply(); editor.apply();
} }
}
}); });
} }
return builder.create(); return builder.create();
@ -157,11 +140,9 @@ 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
public void onClick(View v) {
AutofillPreferenceActivity callingActivity = (AutofillPreferenceActivity) getActivity(); AutofillPreferenceActivity callingActivity = (AutofillPreferenceActivity) getActivity();
Dialog dialog = getDialog(); Dialog dialog = getDialog();
@ -176,7 +157,7 @@ public class AutofillFragment extends DialogFragment {
String packageName = getArguments().getString("packageName", ""); String packageName = getArguments().getString("packageName", "");
if (isWeb) { if (isWeb) {
// handle some errors and don't dismiss the dialog // handle some errors and don't dismiss the dialog
EditText webURL = (EditText) dialog.findViewById(R.id.webURL); EditText webURL = dialog.findViewById(R.id.webURL);
packageName = webURL.getText().toString(); packageName = webURL.getText().toString();
@ -192,7 +173,7 @@ public class AutofillFragment extends DialogFragment {
} }
// write to preferences accordingly // write to preferences accordingly
RadioGroup radioGroup = (RadioGroup) dialog.findViewById(R.id.autofill_radiogroup); RadioGroup radioGroup = dialog.findViewById(R.id.autofill_radiogroup);
switch (radioGroup.getCheckedRadioButtonId()) { switch (radioGroup.getCheckedRadioButtonId()) {
case R.id.use_default: case R.id.use_default:
if (!isWeb) { if (!isWeb) {
@ -231,7 +212,7 @@ public class AutofillFragment extends DialogFragment {
String oldPackageName = getArguments().getString("packageName", ""); String oldPackageName = getArguments().getString("packageName", "");
if (oldPackageName.equals(packageName)) { if (oldPackageName.equals(packageName)) {
callingActivity.recyclerAdapter.notifyItemChanged(position); callingActivity.recyclerAdapter.notifyItemChanged(position);
} else if (oldPackageName.equals("")){ } else if (oldPackageName.equals("")) {
callingActivity.recyclerAdapter.addWebsite(packageName); callingActivity.recyclerAdapter.addWebsite(packageName);
} else { } else {
editor.remove(oldPackageName); editor.remove(oldPackageName);
@ -241,7 +222,6 @@ public class AutofillFragment extends DialogFragment {
} }
dismiss(); dismiss();
}
}); });
} }
} }

View file

@ -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")));
}
});
}
}
} }

View file

@ -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) {
@ -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);
}
}
} }

View file

@ -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
public void onClick(DialogInterface d, int which) {
dialog.dismiss(); dialog.dismiss();
dialog = null; dialog = null;
}
}); });
builder.setPositiveButton(R.string.autofill_paste, new DialogInterface.OnClickListener() { builder.setPositiveButton(R.string.autofill_paste, (d, which) -> {
@Override
public void onClick(DialogInterface d, int which) {
pasteText(node, password.getUsername()); pasteText(node, password.getUsername());
dialog.dismiss(); dialog.dismiss();
dialog = null; dialog = null;
}
}); });
builder.setMessage(getString(R.string.autofill_paste_username, password.getUsername())); builder.setMessage(getString(R.string.autofill_paste_username, password.getUsername()));
@ -436,16 +417,12 @@ 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
public void onClick(DialogInterface d, int which) {
dialog.dismiss(); dialog.dismiss();
dialog = null; 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);
@ -453,7 +430,6 @@ public class AutofillService extends AccessibilityService {
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,9 +440,7 @@ 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
public void onClick(DialogInterface dialog, int which) {
lastWhichItem = which; lastWhichItem = which;
if (which < items.size()) { if (which < items.size()) {
bindDecryptAndVerify(); bindDecryptAndVerify();
@ -484,7 +458,6 @@ public class AutofillService extends AccessibilityService {
intent.putExtra("isWeb", isWeb); intent.putExtra("isWeb", isWeb);
startActivity(intent); startActivity(intent);
} }
}
}); });
dialog = builder.create(); dialog = builder.create();
@ -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();
}
}
} }

View file

@ -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()
@ -187,7 +193,8 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
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)
} }
@ -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,7 +324,8 @@ 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)
@ -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,7 +413,8 @@ 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? ->
when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
OpenPgpApi.RESULT_CODE_SUCCESS -> { OpenPgpApi.RESULT_CODE_SUCCESS -> {
try { try {
// TODO This might fail, we should check that the write is successful // TODO This might fail, we should check that the write is successful
@ -430,7 +444,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
} }
} }
/** /**
* 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
@ -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

View file

@ -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();
} }
} }

View file

@ -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,8 +553,7 @@ 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 { try {
FileUtils.deleteDirectory(localDir); FileUtils.deleteDirectory(localDir);
try { try {
@ -582,14 +574,9 @@ public class GitActivity extends AppCompatActivity {
dialog.cancel(); 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
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(activity, UserPreference.class); Intent intent = new Intent(activity, UserPreference.class);
startActivityForResult(intent, REQUEST_PULL); 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
public void onClick(DialogInterface dialogInterface, int i) {
// do nothing :( // do nothing :(
setResult(RESULT_OK); setResult(RESULT_OK);
finish(); finish();
}
}) })
.show(); .show();

View file

@ -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()) {

View file

@ -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,9 +106,7 @@ 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
public void onClick(DialogInterface dialog, int id) {
try { try {
// Ask the UserPreference to provide us with the ssh-key // Ask the UserPreference to provide us with the ssh-key
// onResult has to be handled by the callingActivity // onResult has to be handled by the callingActivity
@ -122,11 +117,8 @@ public abstract class GitOperation {
System.out.println("Exception caught :("); System.out.println("Exception caught :(");
e.printStackTrace(); 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
public void onClick(DialogInterface dialog, int which) {
try { try {
// Duplicated code // Duplicated code
Intent intent = new Intent(callingActivity.getApplicationContext(), UserPreference.class); Intent intent = new Intent(callingActivity.getApplicationContext(), UserPreference.class);
@ -136,19 +128,15 @@ public abstract class GitOperation {
System.out.println("Exception caught :("); System.out.println("Exception caught :(");
e.printStackTrace(); e.printStackTrace();
} }
}
}) })
.setNeutralButton(callingActivity.getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() { .setNeutralButton(callingActivity.getResources().getString(R.string.dialog_cancel), (dialog, id) -> {
@Override
public void onClick(DialogInterface dialog, int id) {
// Finish the blank GitActivity so user doesn't have to press back // Finish the blank GitActivity so user doesn't have to press back
callingActivity.finish(); 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,8 +160,7 @@ 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) {
@ -186,11 +173,8 @@ public abstract class GitOperation {
// call back the method // call back the method
executeAfterAuthentication(connectionMode, username, sshKey, true); executeAfterAuthentication(connectionMode, username, sshKey, true);
} }
} }).setNegativeButton(callingActivity.getResources().getString(R.string.dialog_cancel), (dialog, whichButton) -> {
}).setNegativeButton(callingActivity.getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
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() {
public void onClick(DialogInterface dialog, int whichButton) {
// Do nothing. // 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
public void onClick(DialogInterface dialogInterface, int i) {
callingActivity.setResult(Activity.RESULT_CANCELED); callingActivity.setResult(Activity.RESULT_CANCELED);
callingActivity.finish(); callingActivity.finish();
}
}).show(); }).show();
} }

View file

@ -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();
} }
} }

View file

@ -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();
} }
} }

View file

@ -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();
} }
} }

View file

@ -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;

View file

@ -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;

View file

@ -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)
}
}
}

View file

@ -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.

View file

@ -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);
}
} }

View file

@ -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
public void onClick(View v) {
listener.onFragmentInteraction(pass); listener.onFragmentInteraction(pass);
notifyItemChanged(holder.getAdapterPosition()); notifyItemChanged(holder.getAdapterPosition());
}
}; };
} }

View file

@ -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);

View file

@ -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", "");
} }

View file

@ -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());
};
}
} }

View file

@ -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;