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
defaultConfig {
applicationId "com.zeapo.pwdstore"
minSdkVersion 16
minSdkVersion 21
targetSdkVersion 28
versionCode 10302
versionName "1.3.2"
@ -15,8 +15,8 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
lintOptions {
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.espresso:espresso-core:3.1.0-alpha4'
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0-alpha4'
}
repositories {
mavenCentral()

View file

@ -4,9 +4,10 @@ import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
class DividerItemDecoration extends RecyclerView.ItemDecoration {
@ -31,7 +32,7 @@ class DividerItemDecoration extends RecyclerView.ItemDecoration {
}
@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 right = parent.getWidth() - parent.getPaddingRight();

View file

@ -11,14 +11,13 @@ import java.io.UnsupportedEncodingException;
public class PasswordEntry {
private static final String[] USERNAME_FIELDS = new String[]{"login", "username"};
private String extraContent;
private final String password;
private final String username;
private final String totpSecret;
private final String hotpSecret;
private final Long hotpCounter;
private final String content;
private String extraContent;
private boolean isIncremented = false;
public PasswordEntry(final ByteArrayOutputStream os) throws UnsupportedEncodingException {
@ -34,7 +33,7 @@ public class PasswordEntry {
hotpCounter = findHotpCounter(content);
extraContent = findExtraContent(passContent);
username = findUsername();
}
}
public String getPassword() {
return password;
@ -76,7 +75,9 @@ public class PasswordEntry {
return hotpSecret != null && hotpCounter != null;
}
public boolean hotpIsIncremented() { return isIncremented; }
public boolean hotpIsIncremented() {
return isIncremented;
}
public void incrementHotp() {
for (String line : content.split("\n")) {
@ -126,7 +127,7 @@ public class PasswordEntry {
return null;
}
private String findExtraContent(String [] passContent) {
private String findExtraContent(String[] passContent) {
String extraContent = passContent.length > 1 ? passContent[1] : "";
// if there is a HOTP URI, we must return the extra content with the counter incremented
if (hasHotp()) {

View file

@ -4,16 +4,16 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
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.view.LayoutInflater;
import android.view.View;
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.PasswordRecyclerAdapter;
import com.zeapo.pwdstore.utils.PasswordRepository;
@ -24,16 +24,12 @@ import java.util.Stack;
/**
* A fragment representing a list of Items.
* <p />
* <p/>
* Large screen devices (such as tablets) are supported by replacing the ListView
* with a GridView.
* <p />
* <p/>
*/
public class PasswordFragment extends Fragment{
public interface OnFragmentInteractionListener {
void onFragmentInteraction(PasswordItem item);
}
public class PasswordFragment extends Fragment {
// store the pass files list in a stack
private Stack<ArrayList<PasswordItem>> passListStack;
@ -43,12 +39,12 @@ public class PasswordFragment extends Fragment{
private RecyclerView recyclerView;
private OnFragmentInteractionListener mListener;
private SharedPreferences settings;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public PasswordFragment() { }
public PasswordFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
@ -60,18 +56,18 @@ public class PasswordFragment extends Fragment{
scrollPosition = new Stack<>();
pathStack = new Stack<>();
recyclerAdapter = new PasswordRecyclerAdapter((PasswordStore) getActivity(), mListener,
PasswordRepository.getPasswords(new File(path), PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()));
PasswordRepository.getPasswords(new File(path), PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()));
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.password_recycler_view, container, false);
// use a linear layout manager
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
recyclerView = (RecyclerView) view.findViewById(R.id.pass_recycler);
recyclerView = view.findViewById(R.id.pass_recycler);
recyclerView.setLayoutManager(mLayoutManager);
// use divider
@ -80,13 +76,8 @@ public class PasswordFragment extends Fragment{
// Set the adapter
recyclerView.setAdapter(recyclerAdapter);
final FloatingActionButton fab = (FloatingActionButton) view.findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
((PasswordStore) getActivity()).createPassword();
}
});
final FloatingActionButton fab = view.findViewById(R.id.fab);
fab.setOnClickListener(v -> ((PasswordStore) getActivity()).createPassword());
registerForContextMenu(recyclerView);
return view;
@ -96,34 +87,32 @@ public class PasswordFragment extends Fragment{
public void onAttach(final Context context) {
super.onAttach(context);
try {
mListener = new OnFragmentInteractionListener() {
public void onFragmentInteraction(PasswordItem item) {
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
// push the current password list (non filtered plz!)
passListStack.push(pathStack.isEmpty() ?
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(context), getSortOrder()) :
PasswordRepository.getPasswords(pathStack.peek(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
//push the category were we're going
pathStack.push(item.getFile());
scrollPosition.push(recyclerView.getVerticalScrollbarPosition());
mListener = item -> {
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
// push the current password list (non filtered plz!)
passListStack.push(pathStack.isEmpty() ?
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(context), getSortOrder()) :
PasswordRepository.getPasswords(pathStack.peek(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
//push the category were we're going
pathStack.push(item.getFile());
scrollPosition.push(recyclerView.getVerticalScrollbarPosition());
recyclerView.scrollToPosition(0);
recyclerAdapter.clear();
recyclerAdapter.addAll(PasswordRepository.getPasswords(item.getFile(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
recyclerView.scrollToPosition(0);
recyclerAdapter.clear();
recyclerAdapter.addAll(PasswordRepository.getPasswords(item.getFile(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
} else {
if (getArguments().getBoolean("matchWith", false)) {
((PasswordStore) getActivity()).matchPasswordWithApp(item);
} else {
if (getArguments().getBoolean("matchWith", false)) {
((PasswordStore) getActivity()).matchPasswordWithApp(item);
} else {
((PasswordStore) getActivity()).decryptPassword(item);
}
((PasswordStore) getActivity()).decryptPassword(item);
}
}
};
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement OnFragmentInteractionListener");
+ " must implement OnFragmentInteractionListener");
}
}
@ -146,12 +135,13 @@ public class PasswordFragment extends Fragment{
public void refreshAdapter() {
recyclerAdapter.clear();
recyclerAdapter.addAll(pathStack.isEmpty() ?
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()) :
PasswordRepository.getPasswords(pathStack.peek(), PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()));
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()) :
PasswordRepository.getPasswords(pathStack.peek(), PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()));
}
/**
* filters the list adapter
*
* @param filter the filter to apply
*/
public void filterAdapter(String filter) {
@ -166,8 +156,9 @@ public class PasswordFragment extends Fragment{
/**
* recursively filters a directory and extract all the matching items
*
* @param filter the filter to apply
* @param dir the directory to filter
* @param dir the directory to filter
*/
private void recursiveFilter(String filter, File dir) {
// on the root the pathStack is empty
@ -205,6 +196,7 @@ public class PasswordFragment extends Fragment{
/**
* gets the current directory
*
* @return the current directory
*/
public File getCurrentDir() {
@ -227,4 +219,8 @@ public class PasswordFragment extends Fragment{
private PasswordRepository.PasswordSortOrder getSortOrder() {
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.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Typeface;
import android.os.Bundle;
@ -30,7 +29,6 @@ public class PasswordGeneratorDialogFragment extends DialogFragment {
public PasswordGeneratorDialogFragment() {
}
@NotNull
@SuppressLint("SetTextI18n")
@Override
@ -64,49 +62,35 @@ public class PasswordGeneratorDialogFragment extends DialogFragment {
TextView textView = view.findViewById(R.id.lengthNumber);
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() {
@Override
public void onClick(DialogInterface dialog, int which) {
EditText edit = callingActivity.findViewById(R.id.crypto_password_edit);
TextView generate = view.findViewById(R.id.passwordText);
edit.setText(generate.getText());
}
builder.setPositiveButton(getResources().getString(R.string.dialog_ok), (dialog, which) -> {
EditText edit = callingActivity.findViewById(R.id.crypto_password_edit);
edit.setText(passwordText.getText());
});
builder.setNegativeButton(getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
builder.setNegativeButton(getResources().getString(R.string.dialog_cancel), (dialog, which) -> {
}
});
builder.setNeutralButton(getResources().getString(R.string.pwgen_generate), null);
final AlertDialog ad = builder.setTitle(this.getResources().getString(R.string.pwgen_title)).create();
ad.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
setPreferences();
TextView textView = view.findViewById(R.id.passwordText);
textView.setText(PasswordGenerator.INSTANCE.generate(getActivity().getApplicationContext()).get(0));
ad.setOnShowListener(dialog -> {
setPreferences();
passwordText.setText(PasswordGenerator.INSTANCE.generate(getActivity().getApplicationContext()).get(0));
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setPreferences();
TextView textView = view.findViewById(R.id.passwordText);
textView.setText(PasswordGenerator.INSTANCE.generate(callingActivity.getApplicationContext()).get(0));
}
});
}
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
b.setOnClickListener(v -> {
setPreferences();
passwordText.setText(PasswordGenerator.INSTANCE.generate(callingActivity.getApplicationContext()).get(0));
});
});
return ad;
}
private void setPreferences () {
private void setPreferences() {
ArrayList<String> preferences = new ArrayList<>();
if (!((CheckBox) getDialog().findViewById(R.id.numerals)).isChecked()) {
preferences.add("0");
@ -127,7 +111,7 @@ public class PasswordGeneratorDialogFragment extends DialogFragment {
try {
int length = Integer.valueOf(editText.getText().toString());
PasswordGenerator.INSTANCE.setPrefs(getActivity().getApplicationContext(), preferences, length);
} catch(NumberFormatException e) {
} catch (NumberFormatException e) {
PasswordGenerator.INSTANCE.setPrefs(getActivity().getApplicationContext(), preferences);
}
}

View file

@ -3,7 +3,6 @@ package com.zeapo.pwdstore;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
@ -14,20 +13,6 @@ import android.graphics.drawable.Icon;
import android.os.Build;
import android.os.Bundle;
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.util.Log;
import android.view.KeyEvent;
@ -35,16 +20,23 @@ import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
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.git.GitActivity;
import com.zeapo.pwdstore.git.GitAsyncTask;
import com.zeapo.pwdstore.git.GitOperation;
import com.zeapo.pwdstore.pwgen.PRNGFixes;
import com.zeapo.pwdstore.utils.PasswordItem;
import com.zeapo.pwdstore.utils.PasswordRecyclerAdapter;
import com.zeapo.pwdstore.utils.PasswordRepository;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.eclipse.jgit.api.Git;
@ -53,21 +45,15 @@ import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
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 {
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_ENCRYPT = 9911;
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_EDIT = 9916;
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) {
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
@ -106,8 +103,6 @@ public class PasswordStore extends AppCompatActivity {
return super.onKeyDown(keyCode, event);
}
private final static int REQUEST_EXTERNAL_STORAGE = 50;
@Override
@SuppressLint("NewApi")
protected void onCreate(Bundle savedInstanceState) {
@ -116,7 +111,6 @@ public class PasswordStore extends AppCompatActivity {
shortcutManager = getSystemService(ShortcutManager.class);
}
activity = this;
PRNGFixes.INSTANCE.apply();
// If user opens app with permission granted then revokes and returns,
// prevent attempt to create password list fragment
@ -142,17 +136,12 @@ public class PasswordStore extends AppCompatActivity {
// 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.LENGTH_INDEFINITE)
.setAction(R.string.dialog_ok, new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_EXTERNAL_STORAGE);
}
});
.setAction(R.string.dialog_ok, view -> ActivityCompat.requestPermissions(activity,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_EXTERNAL_STORAGE));
snack.show();
View view = snack.getView();
TextView tv = (TextView) view.findViewById(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.setMaxLines(10);
} else {
@ -357,12 +346,9 @@ public class PasswordStore extends AppCompatActivity {
if (keyIds.isEmpty())
new AlertDialog.Builder(this)
.setMessage(this.getResources().getString(R.string.key_dialog_text))
.setPositiveButton(this.getResources().getString(R.string.dialog_positive), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(activity, UserPreference.class);
startActivityForResult(intent, GitActivity.REQUEST_INIT);
}
.setPositiveButton(this.getResources().getString(R.string.dialog_positive), (dialogInterface, i) -> {
Intent intent = new Intent(activity, UserPreference.class);
startActivityForResult(intent, GitActivity.REQUEST_INIT);
})
.setNegativeButton(this.getResources().getString(R.string.dialog_negative), null)
.show();
@ -516,24 +502,18 @@ public class PasswordStore extends AppCompatActivity {
if (!PasswordRepository.isInitialized()) {
new AlertDialog.Builder(this)
.setMessage(this.getResources().getString(R.string.creation_dialog_text))
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
}).show();
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)
.setTitle(this.getResources().getString(R.string.no_key_selected_dialog_title))
.setMessage(this.getResources().getString(R.string.no_key_selected_dialog_text))
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(activity, UserPreference.class);
startActivity(intent);
}
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
Intent intent = new Intent(activity, UserPreference.class);
startActivity(intent);
}).show();
return;
}
@ -559,25 +539,19 @@ public class PasswordStore extends AppCompatActivity {
new AlertDialog.Builder(this).
setMessage(this.getResources().getString(R.string.delete_dialog_text) +
item + "\"")
.setPositiveButton(this.getResources().getString(R.string.dialog_yes), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
item.getFile().delete();
adapter.remove(position);
it.remove();
adapter.updateSelectedItems(position, selectedItems);
.setPositiveButton(this.getResources().getString(R.string.dialog_yes), (dialogInterface, i) -> {
item.getFile().delete();
adapter.remove(position);
it.remove();
adapter.updateSelectedItems(position, selectedItems);
commitChange(getResources().getString(R.string.git_commit_remove_text,
item.getLongName()));
deletePasswords(adapter, selectedItems);
}
commitChange(getResources().getString(R.string.git_commit_remove_text,
item.getLongName()));
deletePasswords(adapter, selectedItems);
})
.setNegativeButton(this.getResources().getString(R.string.dialog_no), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
it.remove();
deletePasswords(adapter, selectedItems);
}
.setNegativeButton(this.getResources().getString(R.string.dialog_no), (dialogInterface, i) -> {
it.remove();
deletePasswords(adapter, selectedItems);
})
.show();
}
@ -771,64 +745,54 @@ public class PasswordStore extends AppCompatActivity {
new AlertDialog.Builder(this)
.setTitle(this.getResources().getString(R.string.location_dialog_title))
.setMessage(this.getResources().getString(R.string.location_dialog_text))
.setPositiveButton(this.getResources().getString(R.string.location_hidden), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
settings.edit().putBoolean("git_external", false).apply();
.setPositiveButton(this.getResources().getString(R.string.location_hidden), (dialog, whichButton) -> {
settings.edit().putBoolean("git_external", false).apply();
switch (operation) {
case NEW_REPO_BUTTON:
initializeRepositoryInfo();
break;
case CLONE_REPO_BUTTON:
PasswordRepository.initialize(PasswordStore.this);
switch (operation) {
case NEW_REPO_BUTTON:
initializeRepositoryInfo();
break;
case CLONE_REPO_BUTTON:
PasswordRepository.initialize(PasswordStore.this);
Intent intent = new Intent(activity, GitActivity.class);
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
break;
}
Intent intent = new Intent(activity, GitActivity.class);
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
break;
}
})
.setNegativeButton(this.getResources().getString(R.string.location_sdcard), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
settings.edit().putBoolean("git_external", true).apply();
.setNegativeButton(this.getResources().getString(R.string.location_sdcard), (dialog, whichButton) -> {
settings.edit().putBoolean("git_external", true).apply();
String externalRepo = settings.getString("git_external_repo", null);
String externalRepo = settings.getString("git_external_repo", null);
if (externalRepo == null) {
Intent intent = new Intent(activity, UserPreference.class);
intent.putExtra("operation", "git_external");
startActivityForResult(intent, operation);
} else {
new AlertDialog.Builder(activity)
.setTitle(getResources().getString(R.string.directory_selected_title))
.setMessage(getResources().getString(R.string.directory_selected_message, externalRepo))
.setPositiveButton(getResources().getString(R.string.use), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (operation) {
case NEW_REPO_BUTTON:
initializeRepositoryInfo();
break;
case CLONE_REPO_BUTTON:
PasswordRepository.initialize(PasswordStore.this);
if (externalRepo == null) {
Intent intent = new Intent(activity, UserPreference.class);
intent.putExtra("operation", "git_external");
startActivityForResult(intent, operation);
} else {
new AlertDialog.Builder(activity)
.setTitle(getResources().getString(R.string.directory_selected_title))
.setMessage(getResources().getString(R.string.directory_selected_message, externalRepo))
.setPositiveButton(getResources().getString(R.string.use), (dialog1, which) -> {
switch (operation) {
case NEW_REPO_BUTTON:
initializeRepositoryInfo();
break;
case CLONE_REPO_BUTTON:
PasswordRepository.initialize(PasswordStore.this);
Intent intent = new Intent(activity, GitActivity.class);
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
break;
}
}
})
.setNegativeButton(getResources().getString(R.string.change), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(activity, UserPreference.class);
intent.putExtra("operation", "git_external");
startActivityForResult(intent, operation);
}
}).show();
}
Intent intent = new Intent(activity, GitActivity.class);
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
break;
}
})
.setNegativeButton(getResources().getString(R.string.change), (dialog12, which) -> {
Intent intent = new Intent(activity, UserPreference.class);
intent.putExtra("operation", "git_external");
startActivityForResult(intent, operation);
}).show();
}
})
.show();

View file

@ -2,9 +2,9 @@ package com.zeapo.pwdstore
import android.app.Activity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentManager
import com.zeapo.pwdstore.utils.PasswordRepository

View file

@ -1,50 +1,44 @@
package com.zeapo.pwdstore;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
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.View;
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.PasswordItem;
import com.zeapo.pwdstore.utils.PasswordRepository;
import java.io.File;
import java.util.ArrayList;
import java.util.Stack;
/**
* A fragment representing a list of Items.
* <p />
* <p/>
* Large screen devices (such as tablets) are supported by replacing the ListView
* with a GridView.
* <p />
* <p/>
*/
public class SelectFolderFragment extends Fragment{
public interface OnFragmentInteractionListener {
void onFragmentInteraction(PasswordItem item);
}
public class SelectFolderFragment extends Fragment {
// store the pass files list in a stack
private Stack<File> pathStack;
private FolderRecyclerAdapter recyclerAdapter;
private RecyclerView recyclerView;
private OnFragmentInteractionListener mListener;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public SelectFolderFragment() { }
public SelectFolderFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
@ -53,11 +47,11 @@ public class SelectFolderFragment extends Fragment{
pathStack = new Stack<>();
recyclerAdapter = new FolderRecyclerAdapter((SelectFolderActivity) getActivity(), mListener,
PasswordRepository.getPasswords(new File(path), PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()));
PasswordRepository.getPasswords(new File(path), PasswordRepository.getRepositoryDirectory(getActivity()), getSortOrder()));
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.password_recycler_view, container, false);
@ -81,28 +75,27 @@ public class SelectFolderFragment extends Fragment{
public void onAttach(final Context context) {
super.onAttach(context);
try {
mListener = new OnFragmentInteractionListener() {
public void onFragmentInteraction(PasswordItem item) {
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
//push the category were we're going
pathStack.push(item.getFile());
mListener = item -> {
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
//push the category were we're going
pathStack.push(item.getFile());
recyclerView.scrollToPosition(0);
recyclerAdapter.clear();
recyclerAdapter.addAll(PasswordRepository.getPasswords(item.getFile(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
recyclerView.scrollToPosition(0);
recyclerAdapter.clear();
recyclerAdapter.addAll(PasswordRepository.getPasswords(item.getFile(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
};
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement OnFragmentInteractionListener");
+ " must implement OnFragmentInteractionListener");
}
}
/**
* gets the current directory
*
* @return the current directory
*/
public File getCurrentDir() {
@ -115,4 +108,8 @@ public class SelectFolderFragment extends Fragment{
private PasswordRepository.PasswordSortOrder getSortOrder() {
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.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
@ -24,15 +21,14 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.KeyPair;
import org.apache.commons.io.FileUtils;
import java.io.File;
@ -41,6 +37,34 @@ import java.lang.ref.WeakReference;
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
public static class SshKeyGenFragment extends Fragment {
public SshKeyGenFragment() {
@ -61,18 +85,15 @@ public class SshKeyGen extends AppCompatActivity {
((EditText) v.findViewById(R.id.passphrase)).setTypeface(monoTypeface);
CheckBox checkbox = v.findViewById(R.id.show_passphrase);
checkbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
EditText editText = v.findViewById(R.id.passphrase);
int selection = editText.getSelectionEnd();
if (isChecked) {
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
} else {
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
}
editText.setSelection(selection);
checkbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
EditText editText = v.findViewById(R.id.passphrase);
int selection = editText.getSelectionEnd();
if (isChecked) {
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
} else {
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
}
editText.setSelection(selection);
});
return v;
@ -100,59 +121,31 @@ public class SshKeyGen extends AppCompatActivity {
e.printStackTrace();
}
builder.setPositiveButton(getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (getActivity() instanceof SshKeyGen)
getActivity().finish();
}
builder.setPositiveButton(getResources().getString(R.string.dialog_ok), (dialog, which) -> {
if (getActivity() instanceof SshKeyGen)
getActivity().finish();
});
builder.setNegativeButton(getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
builder.setNegativeButton(getResources().getString(R.string.dialog_cancel), (dialog, which) -> {
}
});
builder.setNeutralButton(getResources().getString(R.string.ssh_keygen_copy), null);
final AlertDialog ad = builder.setTitle("Your public key").create();
ad.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TextView textView = getDialog().findViewById(R.id.public_key);
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("public key", textView.getText().toString());
clipboard.setPrimaryClip(clip);
}
});
}
ad.setOnShowListener(dialog -> {
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
b.setOnClickListener(v1 -> {
TextView textView1 = getDialog().findViewById(R.id.public_key);
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("public key", textView1.getText().toString());
clipboard.setPrimaryClip(clip);
});
});
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 ProgressDialog pd;
private WeakReference<SshKeyGen> weakReference;
@ -211,27 +204,11 @@ public class SshKeyGen extends AppCompatActivity {
new AlertDialog.Builder(weakReference.get())
.setTitle("Error while trying to generate the ssh-key")
.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() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// pass
}
.setPositiveButton(weakReference.get().getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
// pass
}).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
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
class ToCloneOrNot : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
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.PreferenceManager
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.view.MenuItem
import android.view.accessibility.AccessibilityManager
import android.widget.TextView
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.zeapo.pwdstore.autofill.AutofillPreferenceActivity
import com.zeapo.pwdstore.crypto.PgpActivity
@ -36,8 +36,9 @@ import org.openintents.openpgp.util.OpenPgpUtils
import java.io.File
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
import kotlin.collections.HashSet
import java.util.ArrayList
import java.util.Date
import java.util.Locale
class UserPreference : AppCompatActivity() {
private lateinit var prefsFragment: PrefsFragment
@ -75,17 +76,19 @@ class UserPreference : AppCompatActivity() {
true
}
findPreference("ssh_key_clear_passphrase").onPreferenceClickListener = Preference.OnPreferenceClickListener {
sharedPreferences.edit().putString("ssh_key_passphrase", null).apply()
it.isEnabled = false
true
}
findPreference("ssh_key_clear_passphrase").onPreferenceClickListener =
Preference.OnPreferenceClickListener {
sharedPreferences.edit().putString("ssh_key_passphrase", null).apply()
it.isEnabled = false
true
}
findPreference("hotp_remember_clear_choice").onPreferenceClickListener = Preference.OnPreferenceClickListener {
sharedPreferences.edit().putBoolean("hotp_remember_check", false).apply()
it.isEnabled = false
true
}
findPreference("hotp_remember_clear_choice").onPreferenceClickListener =
Preference.OnPreferenceClickListener {
sharedPreferences.edit().putBoolean("hotp_remember_check", false).apply()
it.isEnabled = false
true
}
findPreference("git_server_info").onPreferenceClickListener = Preference.OnPreferenceClickListener {
val intent = Intent(callingActivity, GitActivity::class.java)
@ -104,28 +107,30 @@ class UserPreference : AppCompatActivity() {
findPreference("git_delete_repo").onPreferenceClickListener = Preference.OnPreferenceClickListener {
val repoDir = PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext)
AlertDialog.Builder(callingActivity)
.setTitle(R.string.pref_dialog_delete_title)
.setMessage("${resources.getString(R.string.dialog_delete_msg)} \n $repoDir")
.setCancelable(false)
.setPositiveButton(R.string.dialog_delete) { dialogInterface, _ ->
try {
FileUtils.cleanDirectory(PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext))
PasswordRepository.closeRepository()
} catch (e: Exception) {
//TODO Handle the diffent cases of exceptions
}
.setTitle(R.string.pref_dialog_delete_title)
.setMessage("${resources.getString(R.string.dialog_delete_msg)} \n $repoDir")
.setCancelable(false)
.setPositiveButton(R.string.dialog_delete) { dialogInterface, _ ->
try {
FileUtils.cleanDirectory(PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext))
PasswordRepository.closeRepository()
} catch (e: Exception) {
//TODO Handle the diffent cases of exceptions
}
sharedPreferences.edit().putBoolean("repository_initialized", false).apply()
dialogInterface.cancel()
callingActivity.finish()
}.setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ -> run { dialogInterface.cancel() } }
.show()
sharedPreferences.edit().putBoolean("repository_initialized", false).apply()
dialogInterface.cancel()
callingActivity.finish()
}
.setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ -> run { dialogInterface.cancel() } }
.show()
true
}
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 {
callingActivity.selectExternalGitRepository()
true
@ -148,10 +153,14 @@ class UserPreference : AppCompatActivity() {
}
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)
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
}
@ -164,24 +173,34 @@ class UserPreference : AppCompatActivity() {
override fun onStart() {
super.onStart()
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("git_delete_repo").isEnabled = !sharedPreferences.getBoolean("git_external", false)
findPreference("ssh_key_clear_passphrase").isEnabled = sharedPreferences.getString("ssh_key_passphrase", null)?.isNotEmpty() ?: false
findPreference("hotp_remember_clear_choice").isEnabled = sharedPreferences.getBoolean("hotp_remember_check", false)
findPreference("ssh_key_clear_passphrase").isEnabled = sharedPreferences.getString(
"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 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()) {
keyPref.summary = this.resources.getString(R.string.pref_no_key_selected)
} else {
keyPref.summary = selectedKeys.joinToString(separator = ";") {
s ->
keyPref.summary = selectedKeys.joinToString(separator = ";") { s ->
OpenPgpUtils.convertKeyIdToHex(java.lang.Long.valueOf(s))
}
}
// see if the autofill service is enabled and check the preference accordingly
(findPreference("autofill_enable") as CheckBoxPreference).isChecked = (activity as UserPreference).isServiceEnabled
(findPreference("autofill_enable") as CheckBoxPreference).isChecked =
(activity as UserPreference).isServiceEnabled
}
}
@ -203,24 +222,23 @@ class UserPreference : AppCompatActivity() {
fun selectExternalGitRepository() {
val activity = this
AlertDialog.Builder(this)
.setTitle(this.resources.getString(R.string.external_repository_dialog_title))
.setMessage(this.resources.getString(R.string.external_repository_dialog_text))
.setPositiveButton(R.string.dialog_ok) { _, _ ->
// This always works
val i = Intent(activity.applicationContext, FilePickerActivity::class.java)
// This works if you defined the intent filter
// Intent i = new Intent(Intent.ACTION_GET_CONTENT);
.setTitle(this.resources.getString(R.string.external_repository_dialog_title))
.setMessage(this.resources.getString(R.string.external_repository_dialog_text))
.setPositiveButton(R.string.dialog_ok) { _, _ ->
// This always works
val i = Intent(activity.applicationContext, FilePickerActivity::class.java)
// This works if you defined the intent filter
// Intent i = new Intent(Intent.ACTION_GET_CONTENT);
// Set these depending on your use case. These are the defaults.
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
// Set these depending on your use case. These are the defaults.
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().path)
startActivityForResult(i, SELECT_GIT_DIRECTORY)
}.setNegativeButton(R.string.dialog_cancel, null).show()
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().path)
startActivityForResult(i, SELECT_GIT_DIRECTORY)
}.setNegativeButton(R.string.dialog_cancel, null).show()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -242,9 +260,9 @@ class UserPreference : AppCompatActivity() {
* Opens a file explorer to import the private key
*/
fun getSshKeyWithPermissions(useDefaultPicker: Boolean) = runWithPermissions(
requestedPermission = Manifest.permission.READ_EXTERNAL_STORAGE,
requestCode = REQUEST_EXTERNAL_STORAGE_SSH_KEY,
reason = "We need access to the sd-card to import the ssh-key"
requestedPermission = Manifest.permission.READ_EXTERNAL_STORAGE,
requestCode = REQUEST_EXTERNAL_STORAGE_SSH_KEY,
reason = "We need access to the sd-card to import the ssh-key"
) {
getSshKey(useDefaultPicker)
}
@ -282,9 +300,9 @@ class UserPreference : AppCompatActivity() {
if (ContextCompat.checkSelfPermission(this, requestedPermission) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, requestedPermission)) {
val snack = Snackbar.make(prefsFragment.view, reason, Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.dialog_ok) {
ActivityCompat.requestPermissions(this, arrayOf(requestedPermission), requestCode)
}
.setAction(R.string.dialog_ok) {
ActivityCompat.requestPermissions(this, arrayOf(requestedPermission), requestCode)
}
snack.show()
val view = snack.view
val tv = view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
@ -297,16 +315,15 @@ class UserPreference : AppCompatActivity() {
} else {
body()
}
}
/**
* Exports the passwords after requesting permissions
*/
fun exportPasswordsWithPermissions() = runWithPermissions(
requestedPermission = Manifest.permission.WRITE_EXTERNAL_STORAGE,
requestCode = REQUEST_EXTERNAL_STORAGE_SSH_KEY,
reason = "We need access to the sd-card to export the passwords"
requestedPermission = Manifest.permission.WRITE_EXTERNAL_STORAGE,
requestCode = REQUEST_EXTERNAL_STORAGE_SSH_KEY,
reason = "We need access to the sd-card to export the passwords"
) {
exportPasswords()
}
@ -355,15 +372,16 @@ class UserPreference : AppCompatActivity() {
private val isServiceEnabled: Boolean
get() {
val am = this
.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
val runningServices = am
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC)
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC)
return runningServices.any { "com.zeapo.pwdstore/.autofill.AutofillService" == it.id }
}
override fun onActivityResult(requestCode: Int, resultCode: Int,
data: Intent?) {
override fun onActivityResult(
requestCode: Int, resultCode: Int,
data: Intent?
) {
if (resultCode == Activity.RESULT_OK) {
if (data == null) {
setResult(Activity.RESULT_CANCELED)
@ -376,7 +394,11 @@ class UserPreference : AppCompatActivity() {
val uri: Uri = data.data ?: throw IOException("Unable to open file")
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)
prefs.edit().putBoolean("use_generated_key", false).apply()
@ -388,11 +410,13 @@ class UserPreference : AppCompatActivity() {
finish()
} 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)) { _, _ ->
// pass
}.show()
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
}.show()
}
}
EDIT_GIT_INFO -> {
@ -403,21 +427,23 @@ class UserPreference : AppCompatActivity() {
if (uri?.path == Environment.getExternalStorageDirectory().path) {
// the user wants to use the root of the sdcard as a store...
AlertDialog.Builder(this)
.setTitle("SD-Card root selected")
.setMessage("You have selected the root of your sdcard for the store. " +
"This is extremely dangerous and you will lose your data " +
"as its content will, eventually, be deleted")
.setPositiveButton("Remove everything") { _, _ ->
PreferenceManager.getDefaultSharedPreferences(applicationContext)
.edit()
.putString("git_external_repo", uri?.path)
.apply()
}.setNegativeButton(R.string.dialog_cancel, null).show()
.setTitle("SD-Card root selected")
.setMessage(
"You have selected the root of your sdcard for the store. " +
"This is extremely dangerous and you will lose your data " +
"as its content will, eventually, be deleted"
)
.setPositiveButton("Remove everything") { _, _ ->
PreferenceManager.getDefaultSharedPreferences(applicationContext)
.edit()
.putString("git_external_repo", uri?.path)
.apply()
}.setNegativeButton(R.string.dialog_cancel, null).show()
} else {
PreferenceManager.getDefaultSharedPreferences(applicationContext)
.edit()
.putString("git_external_repo", uri?.path)
.apply()
.edit()
.putString("git_external_repo", uri?.path)
.apply()
}
}
EXPORT_PASSWORDS -> {
@ -433,7 +459,6 @@ class UserPreference : AppCompatActivity() {
} catch (e: IOException) {
Log.d("PWD_EXPORT", "Exception happened : " + e.message)
}
}
}
else -> {

View file

@ -6,11 +6,9 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import com.zeapo.pwdstore.PasswordStore;
import org.eclipse.jgit.util.StringUtils;
import java.util.ArrayList;

View file

@ -5,18 +5,13 @@ import android.app.Activity;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
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.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
@ -24,7 +19,9 @@ import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.RadioGroup;
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.R;
@ -84,12 +81,7 @@ public class AutofillFragment extends DialogFragment {
((ListView) view.findViewById(R.id.matched)).setAdapter(adapter);
// delete items by clicking them
((ListView) view.findViewById(R.id.matched)).setOnItemClickListener(
new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
adapter.remove(adapter.getItem(position));
}
});
(parent, view1, position, id) -> adapter.remove(adapter.getItem(position)));
// set the existing preference, if any
SharedPreferences prefs;
@ -116,36 +108,27 @@ public class AutofillFragment extends DialogFragment {
}
// add items with the + button
View.OnClickListener matchPassword = new View.OnClickListener() {
@Override
public void onClick(View v) {
((RadioButton) view.findViewById(R.id.match)).toggle();
Intent intent = new Intent(getActivity(), PasswordStore.class);
intent.putExtra("matchWith", true);
startActivityForResult(intent, MATCH_WITH);
}
View.OnClickListener matchPassword = v -> {
((RadioButton) view.findViewById(R.id.match)).toggle();
Intent intent = new Intent(getActivity(), PasswordStore.class);
intent.putExtra("matchWith", true);
startActivityForResult(intent, MATCH_WITH);
};
view.findViewById(R.id.matchButton).setOnClickListener(matchPassword);
// write to preferences when OK clicked
builder.setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
builder.setPositiveButton(R.string.dialog_ok, (dialog, which) -> {
}
});
builder.setNegativeButton(R.string.dialog_cancel, null);
final SharedPreferences.Editor editor = prefs.edit();
if (isWeb) {
builder.setNeutralButton(R.string.autofill_apps_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (callingActivity.recyclerAdapter != null
&& packageName != null && !packageName.equals("")) {
editor.remove(packageName);
callingActivity.recyclerAdapter.removeWebsite(packageName);
editor.apply();
}
builder.setNeutralButton(R.string.autofill_apps_delete, (dialog, which) -> {
if (callingActivity.recyclerAdapter != null
&& packageName != null && !packageName.equals("")) {
editor.remove(packageName);
callingActivity.recyclerAdapter.removeWebsite(packageName);
editor.apply();
}
});
}
@ -157,91 +140,88 @@ public class AutofillFragment extends DialogFragment {
public void onStart() {
super.onStart();
AlertDialog ad = (AlertDialog) getDialog();
if(ad != null) {
if (ad != null) {
Button positiveButton = ad.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AutofillPreferenceActivity callingActivity = (AutofillPreferenceActivity) getActivity();
Dialog dialog = getDialog();
positiveButton.setOnClickListener(v -> {
AutofillPreferenceActivity callingActivity = (AutofillPreferenceActivity) getActivity();
Dialog dialog = getDialog();
SharedPreferences prefs;
if (!isWeb) {
prefs = getActivity().getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE);
} else {
prefs = getActivity().getApplicationContext().getSharedPreferences("autofill_web", Context.MODE_PRIVATE);
}
SharedPreferences.Editor editor = prefs.edit();
String packageName = getArguments().getString("packageName", "");
if (isWeb) {
// handle some errors and don't dismiss the dialog
EditText webURL = (EditText) dialog.findViewById(R.id.webURL);
packageName = webURL.getText().toString();
if (packageName.equals("")) {
webURL.setError("URL cannot be blank");
return;
}
String oldPackageName = getArguments().getString("packageName", "");
if (!oldPackageName.equals(packageName) && prefs.getAll().containsKey(packageName)) {
webURL.setError("URL already exists");
return;
}
}
// write to preferences accordingly
RadioGroup radioGroup = (RadioGroup) dialog.findViewById(R.id.autofill_radiogroup);
switch (radioGroup.getCheckedRadioButtonId()) {
case R.id.use_default:
if (!isWeb) {
editor.remove(packageName);
} else {
editor.putString(packageName, "");
}
break;
case R.id.first:
editor.putString(packageName, "/first");
break;
case R.id.never:
editor.putString(packageName, "/never");
break;
default:
StringBuilder paths = new StringBuilder();
for (int i = 0; i < adapter.getCount(); i++) {
paths.append(adapter.getItem(i));
if (i != adapter.getCount()) {
paths.append("\n");
}
}
editor.putString(packageName, paths.toString());
}
editor.apply();
// notify the recycler adapter if it is loaded
if (callingActivity.recyclerAdapter != null) {
int position;
if (!isWeb) {
String appName = getArguments().getString("appName", "");
position = callingActivity.recyclerAdapter.getPosition(appName);
callingActivity.recyclerAdapter.notifyItemChanged(position);
} else {
position = callingActivity.recyclerAdapter.getPosition(packageName);
String oldPackageName = getArguments().getString("packageName", "");
if (oldPackageName.equals(packageName)) {
callingActivity.recyclerAdapter.notifyItemChanged(position);
} else if (oldPackageName.equals("")){
callingActivity.recyclerAdapter.addWebsite(packageName);
} else {
editor.remove(oldPackageName);
callingActivity.recyclerAdapter.updateWebsite(oldPackageName, packageName);
}
}
}
dismiss();
SharedPreferences prefs;
if (!isWeb) {
prefs = getActivity().getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE);
} else {
prefs = getActivity().getApplicationContext().getSharedPreferences("autofill_web", Context.MODE_PRIVATE);
}
SharedPreferences.Editor editor = prefs.edit();
String packageName = getArguments().getString("packageName", "");
if (isWeb) {
// handle some errors and don't dismiss the dialog
EditText webURL = dialog.findViewById(R.id.webURL);
packageName = webURL.getText().toString();
if (packageName.equals("")) {
webURL.setError("URL cannot be blank");
return;
}
String oldPackageName = getArguments().getString("packageName", "");
if (!oldPackageName.equals(packageName) && prefs.getAll().containsKey(packageName)) {
webURL.setError("URL already exists");
return;
}
}
// write to preferences accordingly
RadioGroup radioGroup = dialog.findViewById(R.id.autofill_radiogroup);
switch (radioGroup.getCheckedRadioButtonId()) {
case R.id.use_default:
if (!isWeb) {
editor.remove(packageName);
} else {
editor.putString(packageName, "");
}
break;
case R.id.first:
editor.putString(packageName, "/first");
break;
case R.id.never:
editor.putString(packageName, "/never");
break;
default:
StringBuilder paths = new StringBuilder();
for (int i = 0; i < adapter.getCount(); i++) {
paths.append(adapter.getItem(i));
if (i != adapter.getCount()) {
paths.append("\n");
}
}
editor.putString(packageName, paths.toString());
}
editor.apply();
// notify the recycler adapter if it is loaded
if (callingActivity.recyclerAdapter != null) {
int position;
if (!isWeb) {
String appName = getArguments().getString("appName", "");
position = callingActivity.recyclerAdapter.getPosition(appName);
callingActivity.recyclerAdapter.notifyItemChanged(position);
} else {
position = callingActivity.recyclerAdapter.getPosition(packageName);
String oldPackageName = getArguments().getString("packageName", "");
if (oldPackageName.equals(packageName)) {
callingActivity.recyclerAdapter.notifyItemChanged(position);
} else if (oldPackageName.equals("")) {
callingActivity.recyclerAdapter.addWebsite(packageName);
} else {
editor.remove(oldPackageName);
callingActivity.recyclerAdapter.updateWebsite(oldPackageName, packageName);
}
}
}
dismiss();
});
}
}

View file

@ -8,19 +8,18 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.AsyncTask;
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.MenuItem;
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 java.util.ArrayList;
@ -29,9 +28,8 @@ import java.util.Map;
public class AutofillPreferenceActivity extends AppCompatActivity {
private RecyclerView recyclerView;
AutofillRecyclerAdapter recyclerAdapter; // let fragment have access
private RecyclerView recyclerView;
private PackageManager pm;
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);
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.setLayoutManager(layoutManager);
@ -62,64 +60,8 @@ public class AutofillPreferenceActivity extends AppCompatActivity {
setTitle("Autofill Apps");
final FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@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")));
}
}
});
}
final FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(v -> showDialog("", "", true));
}
@Override
@ -175,4 +117,49 @@ public class AutofillPreferenceActivity extends AppCompatActivity {
df.setArguments(args);
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.pm.PackageManager;
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.View;
import android.view.ViewGroup;
import android.widget.ImageView;
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 java.util.ArrayList;
@ -25,50 +24,6 @@ class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecyclerAdapt
private AutofillPreferenceActivity activity;
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
, AutofillPreferenceActivity activity) {
SortedList.Callback<AppInfo> callback = new SortedListAdapterCallback<AppInfo>(this) {
@ -76,17 +31,17 @@ class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecyclerAdapt
// for the limited add/remove usage for websites
@Override
public int compare(AppInfo o1, AppInfo o2) {
return o1.appName.toLowerCase().compareTo(o2.appName.toLowerCase());
return o1.appName.toLowerCase().compareTo(o2.appName.toLowerCase());
}
@Override
public boolean areContentsTheSame(AppInfo oldItem, AppInfo newItem) {
return oldItem.appName.equals(newItem.appName);
return oldItem.appName.equals(newItem.appName);
}
@Override
public boolean areItemsTheSame(AppInfo item1, AppInfo item2) {
return item1.appName.equals(item2.appName);
return item1.appName.equals(item2.appName);
}
};
this.apps = new SortedList<>(AppInfo.class, callback);
@ -170,7 +125,7 @@ class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecyclerAdapt
}
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.add(new AppInfo(null, packageName, false, null));
}
@ -190,4 +145,48 @@ class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecyclerAdapt
}
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;
import android.accessibilityservice.AccessibilityService;
import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
@ -16,18 +14,16 @@ import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.provider.Settings;
import androidx.appcompat.app.AlertDialog;
import android.util.Log;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import com.zeapo.pwdstore.PasswordEntry;
import com.zeapo.pwdstore.R;
import com.zeapo.pwdstore.utils.PasswordRepository;
import org.apache.commons.io.FileUtils;
import org.openintents.openpgp.IOpenPgpService2;
import org.openintents.openpgp.OpenPgpError;
@ -62,10 +58,6 @@ public class AutofillService extends AccessibilityService {
private PasswordEntry lastPassword;
private long lastPasswordMaxDate;
final class Constants {
static final String TAG = "Keychain";
}
public static AutofillService getInstance() {
return instance;
}
@ -96,11 +88,6 @@ public class AutofillService extends AccessibilityService {
@Override
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
if (lastPassword != null && System.currentTimeMillis() > lastPasswordMaxDate) {
lastPassword = null;
@ -405,20 +392,14 @@ public class AutofillService extends AccessibilityService {
}
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog);
builder.setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
dialog.dismiss();
dialog = null;
}
builder.setNegativeButton(R.string.dialog_cancel, (d, which) -> {
dialog.dismiss();
dialog = null;
});
builder.setPositiveButton(R.string.autofill_paste, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
pasteText(node, password.getUsername());
dialog.dismiss();
dialog = null;
}
builder.setPositiveButton(R.string.autofill_paste, (d, which) -> {
pasteText(node, password.getUsername());
dialog.dismiss();
dialog = null;
});
builder.setMessage(getString(R.string.autofill_paste_username, password.getUsername()));
@ -436,24 +417,19 @@ public class AutofillService extends AccessibilityService {
}
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog);
builder.setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface d, int which) {
dialog.dismiss();
dialog = null;
}
builder.setNegativeButton(R.string.dialog_cancel, (d, which) -> {
dialog.dismiss();
dialog = null;
});
builder.setNeutralButton("Settings", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) { //TODO make icon? gear?
// the user will have to return to the app themselves.
Intent intent = new Intent(AutofillService.this, AutofillPreferenceActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra("packageName", packageName);
intent.putExtra("appName", appName);
intent.putExtra("isWeb", isWeb);
startActivity(intent);
}
builder.setNeutralButton("Settings", (dialog, which) -> {
//TODO make icon? gear?
// the user will have to return to the app themselves.
Intent intent = new Intent(AutofillService.this, AutofillPreferenceActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra("packageName", packageName);
intent.putExtra("appName", appName);
intent.putExtra("isWeb", isWeb);
startActivity(intent);
});
// populate the dialog items, always with pick + pick and match. Could
@ -464,26 +440,23 @@ public class AutofillService extends AccessibilityService {
}
itemNames[items.size()] = getString(R.string.autofill_pick);
itemNames[items.size() + 1] = getString(R.string.autofill_pick_and_match);
builder.setItems(itemNames, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
lastWhichItem = which;
if (which < items.size()) {
bindDecryptAndVerify();
} else if (which == items.size()) {
Intent intent = new Intent(AutofillService.this, AutofillActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra("pick", true);
startActivity(intent);
} else {
lastWhichItem--; // will add one element to items, so lastWhichItem=items.size()+1
Intent intent = new Intent(AutofillService.this, AutofillActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra("pickMatchWith", true);
intent.putExtra("packageName", packageName);
intent.putExtra("isWeb", isWeb);
startActivity(intent);
}
builder.setItems(itemNames, (dialog, which) -> {
lastWhichItem = which;
if (which < items.size()) {
bindDecryptAndVerify();
} else if (which == items.size()) {
Intent intent = new Intent(AutofillService.this, AutofillActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra("pick", true);
startActivity(intent);
} else {
lastWhichItem--; // will add one element to items, so lastWhichItem=items.size()+1
Intent intent = new Intent(AutofillService.this, AutofillActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra("pickMatchWith", true);
intent.putExtra("packageName", packageName);
intent.putExtra("isWeb", isWeb);
startActivity(intent);
}
});
@ -515,18 +488,6 @@ public class AutofillService extends AccessibilityService {
}
private class onBoundListener implements OpenPgpServiceConnection.OnBound {
@Override
public void onBound(IOpenPgpService2 service) {
decryptAndVerify();
}
@Override
public void onError(Exception e) {
e.printStackTrace();
}
}
private void bindDecryptAndVerify() {
if (serviceConnection.getService() == null) {
// 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) {
// if the user focused on something else, take focus back
// but this will open another dialog...hack to ignore this
@ -627,4 +587,20 @@ public class AutofillService extends AccessibilityService {
}
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.AppCompatActivity
import com.zeapo.pwdstore.PasswordEntry
import com.zeapo.pwdstore.PasswordGeneratorDialogFragment
import com.zeapo.pwdstore.R
import com.zeapo.pwdstore.UserPreference
import com.zeapo.pwdstore.PasswordGeneratorDialogFragment
import com.zeapo.pwdstore.utils.Otp
import kotlinx.android.synthetic.main.decrypt_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 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 settings: SharedPreferences by lazy { PreferenceManager.getDefaultSharedPreferences(this) }
@ -122,7 +129,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
crypto_password_category.text = getRelativePath(fullPath, repoPath)
}
}
}
override fun onDestroy() {
@ -147,7 +153,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
android.R.id.home -> {
if(passwordEntry?.hotpIsIncremented() == false) {
if (passwordEntry?.hotpIsIncremented() == false) {
setResult(RESULT_CANCELED)
}
finish()
@ -157,7 +163,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
R.id.edit_password -> editPassword()
R.id.crypto_confirm_add -> encrypt()
R.id.crypto_cancel_add -> {
if(passwordEntry?.hotpIsIncremented() == false) {
if (passwordEntry?.hotpIsIncremented() == false) {
setResult(RESULT_CANCELED)
}
finish()
@ -186,8 +192,9 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
val pi: PendingIntent = result.getParcelableExtra(RESULT_INTENT)
try {
this@PgpActivity.startIntentSenderFromChild(
this@PgpActivity, pi.intentSender, requestCode,
null, 0, 0, 0)
this@PgpActivity, pi.intentSender, requestCode,
null, 0, 0, 0
)
} catch (e: IntentSender.SendIntentException) {
Log.e(TAG, "SendIntentException", e)
}
@ -258,8 +265,8 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
null
} else {
HoldToShowPasswordTransformation(
crypto_password_toggle_show,
Runnable { crypto_password_show.text = entry.password }
crypto_password_toggle_show,
Runnable { crypto_password_show.text = entry.password }
)
}
@ -277,8 +284,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
crypto_copy_username.setOnClickListener { copyUsernameToClipBoard(entry.username) }
crypto_username_show.typeface = monoTypeface
crypto_username_show.text = entry.username
}
else {
} else {
crypto_username_show.visibility = View.GONE
crypto_username_show_label.visibility = View.GONE
crypto_copy_username.visibility = View.GONE
@ -295,8 +301,16 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
crypto_copy_otp.visibility = View.VISIBLE
if (entry.hasTotp()) {
crypto_copy_otp.setOnClickListener { 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))
crypto_copy_otp.setOnClickListener {
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 {
// we only want to calculate and show HOTP if the user requests it
crypto_copy_otp.setOnClickListener {
@ -310,22 +324,23 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
// show a dialog asking permission to update the HOTP counter in the entry
val checkInflater = LayoutInflater.from(this)
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)
dialogBuilder.setView(checkLayout)
dialogBuilder.setMessage(R.string.dialog_update_body)
.setCancelable(false)
.setPositiveButton(R.string.dialog_update_positive) { _, _ ->
run {
calculateAndCommitHotp(entry)
if (rememberCheck.isChecked) {
val editor = settings.edit()
editor.putBoolean("hotp_remember_check", true)
editor.putBoolean("hotp_remember_choice", true)
editor.apply()
}
.setCancelable(false)
.setPositiveButton(R.string.dialog_update_positive) { _, _ ->
run {
calculateAndCommitHotp(entry)
if (rememberCheck.isChecked) {
val editor = settings.edit()
editor.putBoolean("hotp_remember_check", true)
editor.putBoolean("hotp_remember_choice", true)
editor.apply()
}
}
}
.setNegativeButton(R.string.dialog_update_negative) { _, _ ->
run {
calculateHotp(entry)
@ -343,7 +358,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
crypto_otp_show.setText(R.string.hotp_pending)
}
crypto_otp_show.typeface = monoTypeface
} else {
crypto_otp_show.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)) {
copyPasswordToClipBoard()
}
} catch (e: Exception) {
Log.e(TAG, "An Exception occurred", e)
}
@ -370,7 +383,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
*/
private fun encrypt() {
// 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()
editPass = crypto_password_edit.text.toString()
editExtra = crypto_extra_edit.text.toString()
@ -400,37 +413,37 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
val path = if (intent.getBooleanExtra("fromDecrypt", false)) fullPath else "$fullPath/$editName.gpg"
api?.executeApiAsync(data, iStream, oStream) { result: Intent? -> when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
OpenPgpApi.RESULT_CODE_SUCCESS -> {
try {
// TODO This might fail, we should check that the write is successful
val outputStream = FileUtils.openOutputStream(File(path))
outputStream.write(oStream.toByteArray())
outputStream.close()
api?.executeApiAsync(data, iStream, oStream) { result: Intent? ->
when (result?.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
OpenPgpApi.RESULT_CODE_SUCCESS -> {
try {
// TODO This might fail, we should check that the write is successful
val outputStream = FileUtils.openOutputStream(File(path))
outputStream.write(oStream.toByteArray())
outputStream.close()
val returnIntent = Intent()
returnIntent.putExtra("CREATED_FILE", path)
returnIntent.putExtra("NAME", editName)
returnIntent.putExtra("LONG_NAME", getLongName(fullPath, repoPath, this.editName!!))
val returnIntent = Intent()
returnIntent.putExtra("CREATED_FILE", path)
returnIntent.putExtra("NAME", editName)
returnIntent.putExtra("LONG_NAME", getLongName(fullPath, repoPath, this.editName!!))
// if coming from decrypt screen->edit button
if (intent.getBooleanExtra("fromDecrypt", false)) {
returnIntent.putExtra("OPERATION", "EDIT")
returnIntent.putExtra("needCommit", true)
// if coming from decrypt screen->edit button
if (intent.getBooleanExtra("fromDecrypt", false)) {
returnIntent.putExtra("OPERATION", "EDIT")
returnIntent.putExtra("needCommit", true)
}
setResult(RESULT_OK, returnIntent)
finish()
} catch (e: Exception) {
Log.e(TAG, "An Exception occurred", e)
}
setResult(RESULT_OK, returnIntent)
finish()
} catch (e: Exception) {
Log.e(TAG, "An Exception occurred", e)
}
OpenPgpApi.RESULT_CODE_ERROR -> handleError(result)
}
OpenPgpApi.RESULT_CODE_ERROR -> handleError(result)
}
}
}
/**
* Opens EncryptActivity with the information for this file to be edited
*/
@ -467,7 +480,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
private fun checkAndIncrementHotp() {
// we do not want to increment the HOTP counter if the user has edited the entry or has not
// generated an HOTP code
if(intent.getStringExtra("OPERATION") != "EDIT" && passwordEntry?.hotpIsIncremented() == true) {
if (intent.getStringExtra("OPERATION") != "EDIT" && passwordEntry?.hotpIsIncremented() == true) {
editName = name.trim()
editPass = passwordEntry?.password
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))
crypto_otp_show.text = Otp.calculateCode(entry.hotpSecret, entry.hotpCounter + 1)
crypto_extra_show.text = entry.extraContent
}
private fun calculateAndCommitHotp(entry : PasswordEntry) {
private fun calculateAndCommitHotp(entry: PasswordEntry) {
calculateHotp(entry)
entry.incrementHotp()
// we must set the result before encrypt() is called, since in
@ -568,7 +581,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
}
private inner class HoldToShowPasswordTransformation constructor(button: Button, private val onToggle: Runnable) :
PasswordTransformationMethod(), View.OnTouchListener {
PasswordTransformationMethod(), View.OnTouchListener {
private var shown = false
init {
@ -634,7 +647,12 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
sendIntent.action = Intent.ACTION_SEND
sendIntent.putExtra(Intent.EXTRA_TEXT, passwordEntry?.password)
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() {
@ -673,7 +691,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
// This signals the DelayShow task to stop and avoids it having
// to poll the AsyncTask.isCancelled() excessively. If skipClearing
// is true, the cancelled task won't clear the clipboard.
fun cancelAndSignal(skipClearing : Boolean) {
fun cancelAndSignal(skipClearing: Boolean) {
skip = skipClearing
cancelNotify.open()
}
@ -710,7 +728,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
while (current < showTime) {
// Block for 1s or until cancel is signalled
if(cancelNotify.block(1000)) {
if (cancelNotify.block(1000)) {
Log.d("DELAY_SHOW", "Cancelled")
return true
}
@ -734,14 +752,17 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
val handler = Handler()
for (i in 0..18) {
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) {
// 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)
}
passwordEntry = null
@ -771,7 +792,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
* Gets the relative path to the repository
*/
fun getRelativePath(fullPath: String, repositoryPath: String): String =
fullPath.replace(repositoryPath, "").replace("/+".toRegex(), "/")
fullPath.replace(repositoryPath, "").replace("/+".toRegex(), "/")
/**
* Gets the Parent path, relative to the repository

View file

@ -2,10 +2,7 @@ package com.zeapo.pwdstore.git;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import com.zeapo.pwdstore.R;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
@ -79,10 +76,7 @@ public class CloneOperation extends GitOperation {
+ callingActivity.getResources().getString(R.string.jgit_error_dialog_text)
+ errorMessage
+ "\nPlease check the FAQ for possible reasons why this error might occur.").
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
}
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
}).show();
}
}

View file

@ -2,13 +2,10 @@ package com.zeapo.pwdstore.git;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
@ -20,11 +17,11 @@ import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import com.zeapo.pwdstore.R;
import com.zeapo.pwdstore.UserPreference;
import com.zeapo.pwdstore.utils.PasswordRepository;
import org.apache.commons.io.FileUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.RebaseCommand;
@ -39,20 +36,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
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_PUSH = 102;
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_CREATE = 107;
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
protected void onCreate(Bundle savedInstanceState) {
@ -83,8 +75,8 @@ public class GitActivity extends AppCompatActivity {
setContentView(R.layout.activity_git_clone);
setTitle(R.string.title_activity_git_clone);
final Spinner protcol_spinner = (Spinner) findViewById(R.id.clone_protocol);
final Spinner connection_mode_spinner = (Spinner) findViewById(R.id.connection_mode);
final Spinner protcol_spinner = findViewById(R.id.clone_protocol);
final Spinner connection_mode_spinner = findViewById(R.id.connection_mode);
// init the spinner for connection modes
final ArrayAdapter<CharSequence> connection_mode_adapter = ArrayAdapter.createFromResource(this,
@ -157,11 +149,11 @@ public class GitActivity extends AppCompatActivity {
}
// init the server information
final EditText server_url = ((EditText) findViewById(R.id.server_url));
final EditText server_port = ((EditText) findViewById(R.id.server_port));
final EditText server_path = ((EditText) findViewById(R.id.server_path));
final EditText server_user = ((EditText) findViewById(R.id.server_user));
final EditText server_uri = ((EditText) findViewById(R.id.clone_uri));
final EditText server_url = findViewById(R.id.server_url);
final EditText server_port = findViewById(R.id.server_port);
final EditText server_path = findViewById(R.id.server_path);
final EditText server_user = findViewById(R.id.server_user);
final EditText server_uri = findViewById(R.id.clone_uri);
server_url.setText(settings.getString("git_remote_server", ""));
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
*/
private void updateURI() {
EditText uri = (EditText) findViewById(R.id.clone_uri);
EditText server_url = ((EditText) findViewById(R.id.server_url));
EditText server_port = ((EditText) findViewById(R.id.server_port));
EditText server_path = ((EditText) findViewById(R.id.server_path));
EditText server_user = ((EditText) findViewById(R.id.server_user));
EditText uri = findViewById(R.id.clone_uri);
EditText server_url = findViewById(R.id.server_url);
EditText server_port = findViewById(R.id.server_port);
EditText server_path = findViewById(R.id.server_path);
EditText server_user = findViewById(R.id.server_user);
if (uri != null) {
switch (protocol) {
@ -301,7 +293,7 @@ public class GitActivity extends AppCompatActivity {
findViewById(R.id.warn_url).setVisibility(View.GONE);
} 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()) {
warn_url.setText(R.string.warn_malformed_url_port);
warn_url.setVisibility(View.VISIBLE);
@ -342,11 +334,11 @@ public class GitActivity extends AppCompatActivity {
* Splits the information in server_uri into the other fields
*/
private void splitURI() {
EditText server_uri = (EditText) findViewById(R.id.clone_uri);
EditText server_url = ((EditText) findViewById(R.id.server_url));
EditText server_port = ((EditText) findViewById(R.id.server_port));
EditText server_path = ((EditText) findViewById(R.id.server_path));
EditText server_user = ((EditText) findViewById(R.id.server_user));
EditText server_uri = findViewById(R.id.clone_uri);
EditText server_url = findViewById(R.id.server_url);
EditText server_port = findViewById(R.id.server_port);
EditText server_path = findViewById(R.id.server_path);
EditText server_user = findViewById(R.id.server_user);
String uri = server_uri.getText().toString();
Pattern pattern = Pattern.compile("(.+)@([\\w\\d\\.]+):([\\d]+)*(.*)");
@ -361,7 +353,7 @@ public class GitActivity extends AppCompatActivity {
server_port.setText(matcher.group(3));
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()) {
warn_url.setText(R.string.warn_malformed_url_port);
warn_url.setVisibility(View.VISIBLE);
@ -467,8 +459,8 @@ public class GitActivity extends AppCompatActivity {
private void showGitConfig() {
// init the server information
final EditText git_user_name = ((EditText) findViewById(R.id.git_user_name));
final EditText git_user_email = ((EditText) findViewById(R.id.git_user_email));
final EditText git_user_name = findViewById(R.id.git_user_name);
final EditText git_user_email = findViewById(R.id.git_user_email);
git_user_name.setText(settings.getString("git_config_user_name", ""));
git_user_email.setText(settings.getString("git_config_user_email", ""));
@ -476,7 +468,7 @@ public class GitActivity extends AppCompatActivity {
// git status
Repository repo = PasswordRepository.getRepository(PasswordRepository.getRepositoryDirectory(activity.getApplicationContext()));
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 {
ObjectId objectId = repo.resolve(Constants.HEAD);
Ref ref = repo.getRef("refs/heads/master");
@ -532,6 +524,7 @@ public class GitActivity extends AppCompatActivity {
GitAsyncTask tasks = new GitAsyncTask(activity, false, true, this);
tasks.execute(new Git(repo).rebase().setOperation(RebaseCommand.Operation.ABORT));
}
@Override
public void onSuccess() {
showGitConfig();
@ -560,36 +553,30 @@ public class GitActivity extends AppCompatActivity {
setMessage(getResources().getString(R.string.dialog_delete_msg) + " " + localDir.toString()).
setCancelable(false).
setPositiveButton(R.string.dialog_delete,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
(dialog, id) -> {
try {
FileUtils.deleteDirectory(localDir);
try {
FileUtils.deleteDirectory(localDir);
try {
new CloneOperation(localDir, activity)
.setCommand(hostname)
.executeAfterAuthentication(connectionMode, settings.getString("git_remote_username", "git"), new File(getFilesDir() + "/.ssh_key"));
} catch (Exception e) {
//This is what happens when jgit fails :(
//TODO Handle the diffent cases of exceptions
e.printStackTrace();
new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
}
} catch (IOException e) {
//TODO Handle the exception correctly if we are unable to delete the directory...
new CloneOperation(localDir, activity)
.setCommand(hostname)
.executeAfterAuthentication(connectionMode, settings.getString("git_remote_username", "git"), new File(getFilesDir() + "/.ssh_key"));
} catch (Exception e) {
//This is what happens when jgit fails :(
//TODO Handle the diffent cases of exceptions
e.printStackTrace();
new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
}
dialog.cancel();
} catch (IOException e) {
//TODO Handle the exception correctly if we are unable to delete the directory...
e.printStackTrace();
new AlertDialog.Builder(GitActivity.this).setMessage(e.getMessage()).show();
}
dialog.cancel();
}
).
setNegativeButton(R.string.dialog_do_not_delete,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
}
(dialog, id) -> dialog.cancel()
).
show();
} else {
@ -626,20 +613,14 @@ public class GitActivity extends AppCompatActivity {
settings.getString("git_remote_location", "").isEmpty())
new AlertDialog.Builder(this)
.setMessage(activity.getResources().getString(R.string.set_information_dialog_text))
.setPositiveButton(activity.getResources().getString(R.string.dialog_positive), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(activity, UserPreference.class);
startActivityForResult(intent, REQUEST_PULL);
}
.setPositiveButton(activity.getResources().getString(R.string.dialog_positive), (dialogInterface, i) -> {
Intent intent = new Intent(activity, UserPreference.class);
startActivityForResult(intent, REQUEST_PULL);
})
.setNegativeButton(activity.getResources().getString(R.string.dialog_negative), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// do nothing :(
setResult(RESULT_OK);
finish();
}
.setNegativeButton(activity.getResources().getString(R.string.dialog_negative), (dialogInterface, i) -> {
// do nothing :(
setResult(RESULT_OK);
finish();
})
.show();

View file

@ -4,10 +4,8 @@ import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.util.Log;
import com.zeapo.pwdstore.PasswordStore;
import com.zeapo.pwdstore.R;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.GitCommand;
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
if (nbChanges == null || nbChanges > 0)
command.call();
}else if (command instanceof PushCommand) {
} else if (command instanceof PushCommand) {
for (final PushResult result : ((PushCommand) command).call()) {
// Code imported (modified) from Gerrit PushOp, license Apache v2
for (final RemoteRefUpdate rru : result.getRemoteUpdates()) {

View file

@ -2,19 +2,17 @@ package com.zeapo.pwdstore.git;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
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.SshConfigSessionFactory;
import com.zeapo.pwdstore.utils.PasswordRepository;
import org.eclipse.jgit.api.GitCommand;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.JschConfigSessionFactory;
@ -109,46 +106,37 @@ public abstract class GitOperation {
new AlertDialog.Builder(callingActivity)
.setMessage(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_text))
.setTitle(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_title))
.setPositiveButton(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_import), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
try {
// Ask the UserPreference to provide us with the ssh-key
// onResult has to be handled by the callingActivity
Intent intent = new Intent(callingActivity.getApplicationContext(), UserPreference.class);
intent.putExtra("operation", "get_ssh_key");
callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE);
} catch (Exception e) {
System.out.println("Exception caught :(");
e.printStackTrace();
}
.setPositiveButton(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_import), (dialog, id) -> {
try {
// Ask the UserPreference to provide us with the ssh-key
// onResult has to be handled by the callingActivity
Intent intent = new Intent(callingActivity.getApplicationContext(), UserPreference.class);
intent.putExtra("operation", "get_ssh_key");
callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE);
} catch (Exception e) {
System.out.println("Exception caught :(");
e.printStackTrace();
}
})
.setNegativeButton(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_generate), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
// Duplicated code
Intent intent = new Intent(callingActivity.getApplicationContext(), UserPreference.class);
intent.putExtra("operation", "make_ssh_key");
callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE);
} catch (Exception e) {
System.out.println("Exception caught :(");
e.printStackTrace();
}
.setNegativeButton(callingActivity.getResources().getString(R.string.ssh_preferences_dialog_generate), (dialog, which) -> {
try {
// Duplicated code
Intent intent = new Intent(callingActivity.getApplicationContext(), UserPreference.class);
intent.putExtra("operation", "make_ssh_key");
callingActivity.startActivityForResult(intent, GET_SSH_KEY_FROM_CLONE);
} catch (Exception e) {
System.out.println("Exception caught :(");
e.printStackTrace();
}
})
.setNeutralButton(callingActivity.getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
// Finish the blank GitActivity so user doesn't have to press back
callingActivity.finish();
}
.setNeutralButton(callingActivity.getResources().getString(R.string.dialog_cancel), (dialog, id) -> {
// Finish the blank GitActivity so user doesn't have to press back
callingActivity.finish();
}).show();
} else {
LayoutInflater layoutInflater = LayoutInflater.from(callingActivity.getApplicationContext());
@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 String sshKeyPassphrase = settings.getString("ssh_key_passphrase", null);
if (showError) {
@ -172,25 +160,21 @@ public abstract class GitOperation {
.setTitle(callingActivity.getResources().getString(R.string.passphrase_dialog_title))
.setMessage(callingActivity.getResources().getString(R.string.passphrase_dialog_text))
.setView(dialogView)
.setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
if (keyPair.decrypt(passphrase.getText().toString())) {
boolean rememberPassphrase = ((CheckBox) dialogView.findViewById(R.id.sshkey_remember_passphrase)).isChecked();
if (rememberPassphrase) {
settings.edit().putString("ssh_key_passphrase", passphrase.getText().toString()).apply();
}
// Authenticate using the ssh-key and then execute the command
setAuthentication(sshKey, username, passphrase.getText().toString()).execute();
} else {
settings.edit().putString("ssh_key_passphrase", null).apply();
// call back the method
executeAfterAuthentication(connectionMode, username, sshKey, true);
.setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialog, whichButton) -> {
if (keyPair.decrypt(passphrase.getText().toString())) {
boolean rememberPassphrase = ((CheckBox) dialogView.findViewById(R.id.sshkey_remember_passphrase)).isChecked();
if (rememberPassphrase) {
settings.edit().putString("ssh_key_passphrase", passphrase.getText().toString()).apply();
}
// Authenticate using the ssh-key and then execute the command
setAuthentication(sshKey, username, passphrase.getText().toString()).execute();
} else {
settings.edit().putString("ssh_key_passphrase", null).apply();
// call back the method
executeAfterAuthentication(connectionMode, username, sshKey, true);
}
}).setNegativeButton(callingActivity.getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Do nothing.
}
}).setNegativeButton(callingActivity.getResources().getString(R.string.dialog_cancel), (dialog, whichButton) -> {
// Do nothing.
}).show();
}
} else {
@ -200,11 +184,8 @@ public abstract class GitOperation {
new AlertDialog.Builder(callingActivity)
.setTitle("Unable to open the ssh-key")
.setMessage("Please check that it was imported.")
.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
.setPositiveButton("Ok", (dialogInterface, i) -> {
}
}).show();
}
}
@ -218,16 +199,12 @@ public abstract class GitOperation {
.setTitle(callingActivity.getResources().getString(R.string.passphrase_dialog_title))
.setMessage(callingActivity.getResources().getString(R.string.password_dialog_text))
.setView(password)
.setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// authenticate using the user/pwd and then execute the command
setAuthentication(username, password.getText().toString()).execute();
.setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialog, whichButton) -> {
// authenticate using the user/pwd and then execute the command
setAuthentication(username, password.getText().toString()).execute();
}
}).setNegativeButton(callingActivity.getResources().getString(R.string.dialog_cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Do nothing.
}
}).setNegativeButton(callingActivity.getResources().getString(R.string.dialog_cancel), (dialog, whichButton) -> {
// Do nothing.
}).show();
}
}
@ -239,12 +216,9 @@ public abstract class GitOperation {
new AlertDialog.Builder(callingActivity).
setTitle(callingActivity.getResources().getString(R.string.jgit_error_dialog_title)).
setMessage(callingActivity.getResources().getString(R.string.jgit_error_dialog_text) + errorMessage).
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
callingActivity.setResult(Activity.RESULT_CANCELED);
callingActivity.finish();
}
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
callingActivity.setResult(Activity.RESULT_CANCELED);
callingActivity.finish();
}).show();
}

View file

@ -2,13 +2,9 @@ package com.zeapo.pwdstore.git;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import com.zeapo.pwdstore.R;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PullCommand;
import org.eclipse.jgit.merge.MergeStrategy;
import java.io.File;
@ -26,6 +22,7 @@ public class PullOperation extends GitOperation {
/**
* Sets the command
*
* @return the current object
*/
public PullOperation setCommand() {
@ -52,11 +49,6 @@ public class PullOperation extends GitOperation {
+ callingActivity.getResources().getString(R.string.jgit_error_dialog_text)
+ errorMessage
+ "\nPlease check the FAQ for possible reasons why this error might occur.").
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
callingActivity.finish();
}
}).show();
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> callingActivity.finish()).show();
}
}

View file

@ -2,10 +2,7 @@ package com.zeapo.pwdstore.git;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import com.zeapo.pwdstore.R;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.PushCommand;
@ -25,6 +22,7 @@ public class PushOperation extends GitOperation {
/**
* Sets the command
*
* @return the current object
*/
public PushOperation setCommand() {
@ -49,11 +47,6 @@ public class PushOperation extends GitOperation {
new AlertDialog.Builder(callingActivity).
setTitle(callingActivity.getResources().getString(R.string.jgit_error_dialog_title)).
setMessage(callingActivity.getString(R.string.jgit_error_push_dialog_text) + errorMessage).
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
callingActivity.finish();
}
}).show();
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> callingActivity.finish()).show();
}
}

View file

@ -2,10 +2,7 @@ package com.zeapo.pwdstore.git;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import com.zeapo.pwdstore.R;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.Git;
@ -64,11 +61,6 @@ public class SyncOperation extends GitOperation {
+ callingActivity.getResources().getString(R.string.jgit_error_dialog_text)
+ errorMessage
+ "\nPlease check the FAQ for possible reasons why this error might occur.").
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
callingActivity.finish();
}
}).show();
setPositiveButton(callingActivity.getResources().getString(R.string.dialog_ok), (dialogInterface, 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.JSchException;
import com.jcraft.jsch.Session;
import org.eclipse.jgit.transport.JschConfigSessionFactory;
import org.eclipse.jgit.transport.OpenSshConfig;
import org.eclipse.jgit.util.FS;
@ -15,8 +14,7 @@ public class GitConfigSessionFactory extends JschConfigSessionFactory {
}
@Override
protected JSch
getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException {
protected JSch getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException {
JSch jsch = super.getJSch(hc, fs);
jsch.removeAllIdentity();
return jsch;

View file

@ -4,7 +4,6 @@ import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.transport.CredentialItem;
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
import android.content.Context
import android.content.SharedPreferences
import java.util.ArrayList
object PasswordGenerator {
internal val DIGITS = 0x0001
internal val UPPERS = 0x0002
internal val SYMBOLS = 0x0004
internal val AMBIGUOUS = 0x0008
internal val NO_VOWELS = 0x0010
internal const val DIGITS = 0x0001
internal const val UPPERS = 0x0002
internal const val SYMBOLS = 0x0004
internal const val AMBIGUOUS = 0x0008
internal const val NO_VOWELS = 0x0010
internal val DIGITS_STR = "0123456789"
internal val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
internal val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
internal val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
internal val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
internal val VOWELS_STR = "01aeiouyAEIOUY"
internal const val DIGITS_STR = "0123456789"
internal const val UPPERS_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
internal const val LOWERS_STR = "abcdefghijklmnopqrstuvwxyz"
internal const val SYMBOLS_STR = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
internal const val AMBIGUOUS_STR = "B8G6I1l0OQDS5Z2"
internal const val VOWELS_STR = "01aeiouyAEIOUY"
// No a, c, n, h, H, C, 1, N
private val pwOptions = "0ABsvy"
private const val pwOptions = "0ABsvy"
/**
* Sets password generation preferences.

View file

@ -3,15 +3,14 @@ package com.zeapo.pwdstore.utils;
import android.annotation.SuppressLint;
import android.app.Activity;
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.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.zeapo.pwdstore.R;
import java.util.ArrayList;
@ -19,9 +18,9 @@ import java.util.Set;
import java.util.TreeSet;
public abstract class EntryRecyclerAdapter extends RecyclerView.Adapter<EntryRecyclerAdapter.ViewHolder> {
final Set<Integer> selectedItems = new TreeSet<>();
private final Activity activity;
private final ArrayList<PasswordItem> values;
final Set<Integer> selectedItems = new TreeSet<>();
EntryRecyclerAdapter(Activity activity, ArrayList<PasswordItem> values) {
this.activity = activity;
@ -85,18 +84,13 @@ public abstract class EntryRecyclerAdapter extends RecyclerView.Adapter<EntryRec
@NonNull
View.OnLongClickListener getOnLongClickListener(ViewHolder holder, PasswordItem pass) {
return new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
};
return v -> false;
}
// Replace the contents of a view (invoked by the layout manager)
@SuppressLint("SetTextI18n")
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
public void onBindViewHolder(@NonNull final ViewHolder holder, int position) {
final PasswordItem pass = getValues().get(position);
holder.name.setText(pass.toString());
if (pass.getType() == PasswordItem.TYPE_CATEGORY) {
@ -127,6 +121,17 @@ public abstract class EntryRecyclerAdapter extends RecyclerView.Adapter<EntryRec
@NonNull
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
// 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
@ -140,19 +145,9 @@ public abstract class EntryRecyclerAdapter extends RecyclerView.Adapter<EntryRec
ViewHolder(View v) {
super(v);
view = v;
name = (TextView) view.findViewById(R.id.label);
type = (TextView) view.findViewById(R.id.type);
typeImage = (ImageView) view.findViewById(R.id.type_image);
name = view.findViewById(R.id.label);
type = view.findViewById(R.id.type);
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;
import androidx.annotation.NonNull;
import android.view.View;
import androidx.annotation.NonNull;
import com.zeapo.pwdstore.SelectFolderActivity;
import com.zeapo.pwdstore.SelectFolderFragment;
@ -19,12 +18,9 @@ public class FolderRecyclerAdapter extends EntryRecyclerAdapter {
@NonNull
protected View.OnClickListener getOnClickListener(final ViewHolder holder, final PasswordItem pass) {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onFragmentInteraction(pass);
notifyItemChanged(holder.getAdapterPosition());
}
return v -> {
listener.onFragmentInteraction(pass);
notifyItemChanged(holder.getAdapterPosition());
};
}

View file

@ -1,18 +1,16 @@
package com.zeapo.pwdstore.utils;
import android.util.Log;
import org.apache.commons.codec.binary.Base32;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class Otp {
public static final int TIME_WINDOW = 30;
@ -27,7 +25,7 @@ public class Otp {
public static String calculateCode(String secret, long counter) {
SecretKeySpec signingKey = new SecretKeySpec(BASE_32.decode(secret), ALGORITHM);
Mac mac = null;
Mac mac;
try {
mac = Mac.getInstance(ALGORITHM);
mac.init(signingKey);

View file

@ -1,8 +1,7 @@
package com.zeapo.pwdstore.utils;
import com.zeapo.pwdstore.crypto.PgpActivity;
import androidx.annotation.NonNull;
import com.zeapo.pwdstore.crypto.PgpActivity;
import java.io.File;
@ -18,8 +17,9 @@ public class PasswordItem implements Comparable {
private final String fullPathToParent;
private final String longName;
/** Create a password item
*
/**
* Create a password item
* <p>
* Make it protected so that we use a builder
*/
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());
}
/** Create a new Category item
/**
* Create a new Category item
*/
public static PasswordItem newCategory(String name, File file, PasswordItem parent, 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) {
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);
}
/** Create a new parentless password item
/**
* Create a new parentless password item
*/
public static PasswordItem newPassword(String name, File file, File rootDir) {
return new PasswordItem(name, null, TYPE_PASSWORD, file, rootDir);
}
public char getType(){
public char getType() {
return this.type;
}
String getName(){
String getName() {
return this.name;
}
@ -81,8 +85,9 @@ public class PasswordItem implements Comparable {
return longName;
}
@NonNull
@Override
public String toString(){
public String toString() {
return this.getName().replace(".gpg", "");
}

View file

@ -1,11 +1,10 @@
package com.zeapo.pwdstore.utils;
import androidx.annotation.NonNull;
import androidx.appcompat.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.view.ActionMode;
import com.zeapo.pwdstore.PasswordFragment;
import com.zeapo.pwdstore.PasswordStore;
import com.zeapo.pwdstore.R;
@ -19,63 +18,6 @@ public class PasswordRecyclerAdapter extends EntryRecyclerAdapter {
private final PasswordFragment.OnFragmentInteractionListener listener;
public ActionMode mActionMode;
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() {
// 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);
}
};
// 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.preference.PreferenceManager;
import android.util.Log;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Repository;
@ -269,9 +268,7 @@ public class PasswordRepository {
return (p2.getType() + p1.getName())
.compareToIgnoreCase(p1.getType() + p2.getName());
}
})
;
});
private Comparator<PasswordItem> comparator;