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:
parent
d5e589d7e4
commit
c200566bca
34 changed files with 923 additions and 1426 deletions
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 -> {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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")));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,310 +0,0 @@
|
|||
package com.zeapo.pwdstore.pwgen
|
||||
|
||||
/*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will Google be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, as long as the origin is not misrepresented.
|
||||
*/
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.Provider
|
||||
import java.security.SecureRandom
|
||||
import java.security.SecureRandomSpi
|
||||
import java.security.Security
|
||||
|
||||
/**
|
||||
* Fixes for the output of the default PRNG having low entropy.
|
||||
*
|
||||
* The fixes need to be applied via [.apply] before any use of Java
|
||||
* Cryptography Architecture primitives. A good place to invoke them is in the
|
||||
* application's `onCreate`.
|
||||
*/
|
||||
object PRNGFixes {
|
||||
|
||||
private const val VERSION_CODE_JELLY_BEAN_MR2 = 18
|
||||
private val BUILD_FINGERPRINT_AND_DEVICE_SERIAL = buildFingerprintAndDeviceSerial
|
||||
|
||||
private val buildFingerprintAndDeviceSerial: ByteArray
|
||||
get() {
|
||||
val result = StringBuilder()
|
||||
val fingerprint = Build.FINGERPRINT
|
||||
if (fingerprint != null) {
|
||||
result.append(fingerprint)
|
||||
}
|
||||
// TODO: Build#SERIAL is deprecated and should be removed or replaced
|
||||
val serial = Build.SERIAL
|
||||
if (serial != null) {
|
||||
result.append(serial)
|
||||
}
|
||||
try {
|
||||
return result.toString().toByteArray(charset("UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
throw RuntimeException("UTF-8 encoding not supported")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all fixes.
|
||||
*
|
||||
* @throws SecurityException if a fix is needed but could not be applied.
|
||||
*/
|
||||
fun apply() {
|
||||
applyOpenSSLFix()
|
||||
installLinuxPRNGSecureRandom()
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the
|
||||
* fix is not needed.
|
||||
*
|
||||
* @throws SecurityException if the fix is needed but could not be applied.
|
||||
*/
|
||||
@Throws(SecurityException::class)
|
||||
private fun applyOpenSSLFix() {
|
||||
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
|
||||
// No need to apply the fix
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// Mix in the device- and invocation-specific seed.
|
||||
Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto")
|
||||
.getMethod("RAND_seed", ByteArray::class.java)
|
||||
.invoke(null, generateSeed())
|
||||
|
||||
// Mix output of Linux PRNG into OpenSSL's PRNG
|
||||
val bytesRead = Class.forName(
|
||||
"org.apache.harmony.xnet.provider.jsse.NativeCrypto"
|
||||
)
|
||||
.getMethod("RAND_load_file", String::class.java, Long::class.javaPrimitiveType)
|
||||
.invoke(null, "/dev/urandom", 1024) as Int
|
||||
if (bytesRead != 1024) {
|
||||
throw IOException(
|
||||
"Unexpected number of bytes read from Linux PRNG: $bytesRead"
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw SecurityException("Failed to seed OpenSSL PRNG", e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs a Linux PRNG-backed `SecureRandom` implementation as the
|
||||
* default. Does nothing if the implementation is already the default or if
|
||||
* there is not need to install the implementation.
|
||||
*
|
||||
* @throws SecurityException if the fix is needed but could not be applied.
|
||||
*/
|
||||
@Throws(SecurityException::class)
|
||||
private fun installLinuxPRNGSecureRandom() {
|
||||
if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) {
|
||||
// No need to apply the fix
|
||||
return
|
||||
}
|
||||
|
||||
// Install a Linux PRNG-based SecureRandom implementation as the
|
||||
// default, if not yet installed.
|
||||
val secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG")
|
||||
if (secureRandomProviders == null
|
||||
|| secureRandomProviders.isEmpty()
|
||||
|| LinuxPRNGSecureRandomProvider::class.java != secureRandomProviders[0].javaClass
|
||||
) {
|
||||
Security.insertProviderAt(LinuxPRNGSecureRandomProvider(), 1)
|
||||
}
|
||||
|
||||
// Assert that new SecureRandom() and
|
||||
// SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed
|
||||
// by the Linux PRNG-based SecureRandom implementation.
|
||||
val rng1 = SecureRandom()
|
||||
if (LinuxPRNGSecureRandomProvider::class.java != rng1.provider.javaClass) {
|
||||
throw SecurityException(
|
||||
"new SecureRandom() backed by wrong Provider: " + rng1.provider.javaClass
|
||||
)
|
||||
}
|
||||
|
||||
val rng2: SecureRandom
|
||||
try {
|
||||
rng2 = SecureRandom.getInstance("SHA1PRNG")
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
throw SecurityException("SHA1PRNG not available", e)
|
||||
}
|
||||
|
||||
if (LinuxPRNGSecureRandomProvider::class.java != rng2.provider.javaClass) {
|
||||
throw SecurityException(
|
||||
"SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong"
|
||||
+ " Provider: " + rng2.provider.javaClass
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `Provider` of `SecureRandom` engines which pass through
|
||||
* all requests to the Linux PRNG.
|
||||
*/
|
||||
private class LinuxPRNGSecureRandomProvider :
|
||||
Provider("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses" + " /dev/urandom") {
|
||||
init {
|
||||
// Although /dev/urandom is not a SHA-1 PRNG, some apps
|
||||
// explicitly request a SHA1PRNG SecureRandom and we thus need to
|
||||
// prevent them from getting the default implementation whose output
|
||||
// may have low entropy.
|
||||
put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom::class.java.name)
|
||||
put("SecureRandom.SHA1PRNG ImplementedIn", "Software")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [SecureRandomSpi] which passes all requests to the Linux PRNG
|
||||
* (`/dev/urandom`).
|
||||
*/
|
||||
class LinuxPRNGSecureRandom : SecureRandomSpi() {
|
||||
|
||||
/**
|
||||
* Whether this engine instance has been seeded. This is needed because
|
||||
* each instance needs to seed itself if the client does not explicitly
|
||||
* seed it.
|
||||
*/
|
||||
private var mSeeded: Boolean = false
|
||||
|
||||
private// NOTE: Consider inserting a BufferedInputStream between
|
||||
// DataInputStream and FileInputStream if you need higher
|
||||
// PRNG output performance and can live with future PRNG
|
||||
// output being pulled into this process prematurely.
|
||||
val urandomInputStream: DataInputStream
|
||||
get() = synchronized(sLock) {
|
||||
if (sUrandomIn == null) {
|
||||
try {
|
||||
sUrandomIn = DataInputStream(
|
||||
FileInputStream(URANDOM_FILE)
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
throw SecurityException(
|
||||
"Failed to open "
|
||||
+ URANDOM_FILE + " for reading", e
|
||||
)
|
||||
}
|
||||
}
|
||||
return sUrandomIn as DataInputStream
|
||||
}
|
||||
|
||||
private val urandomOutputStream: OutputStream
|
||||
@Throws(IOException::class)
|
||||
get() = synchronized(sLock) {
|
||||
if (sUrandomOut == null) {
|
||||
sUrandomOut = FileOutputStream(URANDOM_FILE)
|
||||
}
|
||||
return sUrandomOut as OutputStream
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun engineSetSeed(bytes: ByteArray) {
|
||||
try {
|
||||
val out: OutputStream = urandomOutputStream
|
||||
out.write(bytes)
|
||||
out.flush()
|
||||
} catch (e: IOException) {
|
||||
// On a small fraction of devices /dev/urandom is not writable.
|
||||
// Log and ignore.
|
||||
Log.w(
|
||||
PRNGFixes::class.java.simpleName,
|
||||
"Failed to mix seed into $URANDOM_FILE"
|
||||
)
|
||||
} finally {
|
||||
mSeeded = true
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun engineNextBytes(bytes: ByteArray) {
|
||||
if (!mSeeded) {
|
||||
// Mix in the device- and invocation-specific seed.
|
||||
engineSetSeed(generateSeed())
|
||||
}
|
||||
|
||||
try {
|
||||
val `in`: DataInputStream = urandomInputStream
|
||||
synchronized(`in`) {
|
||||
`in`.readFully(bytes)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
throw SecurityException(
|
||||
"Failed to read from $URANDOM_FILE", e
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun engineGenerateSeed(size: Int): ByteArray {
|
||||
val seed = ByteArray(size)
|
||||
engineNextBytes(seed)
|
||||
return seed
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/*
|
||||
* IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed
|
||||
* are passed through to the Linux PRNG (/dev/urandom). Instances of
|
||||
* this class seed themselves by mixing in the current time, PID, UID,
|
||||
* build fingerprint, and hardware serial number (where available) into
|
||||
* Linux PRNG.
|
||||
*
|
||||
* Concurrency: Read requests to the underlying Linux PRNG are
|
||||
* serialized (on sLock) to ensure that multiple threads do not get
|
||||
* duplicated PRNG output.
|
||||
*/
|
||||
|
||||
private val URANDOM_FILE = File("/dev/urandom")
|
||||
|
||||
private val sLock = Any()
|
||||
|
||||
/**
|
||||
* Input stream for reading from Linux PRNG or `null` if not yet
|
||||
* opened.
|
||||
*/
|
||||
private var sUrandomIn: DataInputStream? = null
|
||||
|
||||
/**
|
||||
* Output stream for writing to Linux PRNG or `null` if not yet
|
||||
* opened.
|
||||
*/
|
||||
private var sUrandomOut: OutputStream? = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a device- and invocation-specific seed to be mixed into the
|
||||
* Linux PRNG.
|
||||
*/
|
||||
private fun generateSeed(): ByteArray {
|
||||
try {
|
||||
val seedBuffer = ByteArrayOutputStream()
|
||||
val seedBufferOut = DataOutputStream(seedBuffer)
|
||||
seedBufferOut.writeLong(System.currentTimeMillis())
|
||||
seedBufferOut.writeLong(System.nanoTime())
|
||||
seedBufferOut.writeInt(Process.myPid())
|
||||
seedBufferOut.writeInt(Process.myUid())
|
||||
seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL)
|
||||
seedBufferOut.close()
|
||||
return seedBuffer.toByteArray()
|
||||
} catch (e: IOException) {
|
||||
throw SecurityException("Failed to generate seed", e)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +1,25 @@
|
|||
package com.zeapo.pwdstore.pwgen
|
||||
|
||||
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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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", "");
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue