Add Spotless to regulate codestyle (#550)
* Add Spotless to regulate codestyle * treewide: Run spotless * Add spotlessCheck to CI test Signed-off-by: Harsh Shandilya <msfjarvis@gmail.com>
This commit is contained in:
parent
9a1a54a6fc
commit
f1f59dc1ed
40 changed files with 907 additions and 682 deletions
2
.github/workflows/push.yml
vendored
2
.github/workflows/push.yml
vendored
|
@ -8,4 +8,4 @@ jobs:
|
||||||
- name: setup-android
|
- name: setup-android
|
||||||
uses: msfjarvis/setup-android@0.2
|
uses: msfjarvis/setup-android@0.2
|
||||||
with:
|
with:
|
||||||
gradleTasks: build test lintDebug -Dpre-dex=false
|
gradleTasks: spotlessCheck build test lintDebug -Dpre-dex=false
|
||||||
|
|
|
@ -40,7 +40,7 @@ class LaunchActivity : AppCompatActivity() {
|
||||||
decryptIntent.putExtra("LAST_CHANGED_TIMESTAMP", intent.getLongExtra("LAST_CHANGED_TIMESTAMP", 0L))
|
decryptIntent.putExtra("LAST_CHANGED_TIMESTAMP", intent.getLongExtra("LAST_CHANGED_TIMESTAMP", 0L))
|
||||||
decryptIntent.putExtra("OPERATION", "DECRYPT")
|
decryptIntent.putExtra("OPERATION", "DECRYPT")
|
||||||
startActivity(decryptIntent)
|
startActivity(decryptIntent)
|
||||||
}else {
|
} else {
|
||||||
startActivity(Intent(this, PasswordStore::class.java))
|
startActivity(Intent(this, PasswordStore::class.java))
|
||||||
}
|
}
|
||||||
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
|
||||||
|
|
|
@ -6,7 +6,6 @@ import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
@ -14,22 +13,21 @@ import androidx.preference.PreferenceManager;
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.zeapo.pwdstore.utils.PasswordItem;
|
import com.zeapo.pwdstore.utils.PasswordItem;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRecyclerAdapter;
|
import com.zeapo.pwdstore.utils.PasswordRecyclerAdapter;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment representing a list of Items.
|
* A fragment representing a list of Items.
|
||||||
* <p/>
|
*
|
||||||
* Large screen devices (such as tablets) are supported by replacing the ListView
|
* <p>Large screen devices (such as tablets) are supported by replacing the ListView with a
|
||||||
* with a GridView.
|
* GridView.
|
||||||
* <p/>
|
*
|
||||||
|
* <p>
|
||||||
*/
|
*/
|
||||||
public class PasswordFragment extends Fragment {
|
public class PasswordFragment extends Fragment {
|
||||||
|
|
||||||
|
@ -43,11 +41,10 @@ public class PasswordFragment extends Fragment {
|
||||||
private SharedPreferences settings;
|
private SharedPreferences settings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
* Mandatory empty constructor for the fragment manager to instantiate the fragment (e.g. upon
|
||||||
* fragment (e.g. upon screen orientation changes).
|
* screen orientation changes).
|
||||||
*/
|
*/
|
||||||
public PasswordFragment() {
|
public PasswordFragment() {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -58,13 +55,19 @@ public class PasswordFragment extends Fragment {
|
||||||
passListStack = new Stack<>();
|
passListStack = new Stack<>();
|
||||||
scrollPosition = new Stack<>();
|
scrollPosition = new Stack<>();
|
||||||
pathStack = new Stack<>();
|
pathStack = new Stack<>();
|
||||||
recyclerAdapter = new PasswordRecyclerAdapter((PasswordStore) requireActivity(), mListener,
|
recyclerAdapter =
|
||||||
PasswordRepository.getPasswords(new File(path), PasswordRepository.getRepositoryDirectory(requireContext()), getSortOrder()));
|
new PasswordRecyclerAdapter(
|
||||||
|
(PasswordStore) requireActivity(),
|
||||||
|
mListener,
|
||||||
|
PasswordRepository.getPasswords(
|
||||||
|
new File(path),
|
||||||
|
PasswordRepository.getRepositoryDirectory(requireContext()),
|
||||||
|
getSortOrder()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(
|
||||||
Bundle savedInstanceState) {
|
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.password_recycler_view, container, false);
|
View view = inflater.inflate(R.layout.password_recycler_view, container, false);
|
||||||
|
|
||||||
// use a linear layout manager
|
// use a linear layout manager
|
||||||
|
@ -74,7 +77,8 @@ public class PasswordFragment extends Fragment {
|
||||||
recyclerView.setLayoutManager(mLayoutManager);
|
recyclerView.setLayoutManager(mLayoutManager);
|
||||||
|
|
||||||
// use divider
|
// use divider
|
||||||
recyclerView.addItemDecoration(new DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL));
|
recyclerView.addItemDecoration(
|
||||||
|
new DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL));
|
||||||
|
|
||||||
// Set the adapter
|
// Set the adapter
|
||||||
recyclerView.setAdapter(recyclerAdapter);
|
recyclerView.setAdapter(recyclerAdapter);
|
||||||
|
@ -90,55 +94,77 @@ public class PasswordFragment extends Fragment {
|
||||||
public void onAttach(final Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
try {
|
try {
|
||||||
mListener = item -> {
|
mListener =
|
||||||
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
|
item -> {
|
||||||
// push the current password list (non filtered plz!)
|
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
|
||||||
passListStack.push(pathStack.isEmpty() ?
|
// push the current password list (non filtered plz!)
|
||||||
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(context), getSortOrder()) :
|
passListStack.push(
|
||||||
PasswordRepository.getPasswords(pathStack.peek(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
|
pathStack.isEmpty()
|
||||||
//push the category were we're going
|
? PasswordRepository.getPasswords(
|
||||||
pathStack.push(item.getFile());
|
PasswordRepository.getRepositoryDirectory(
|
||||||
scrollPosition.push(recyclerView.getVerticalScrollbarPosition());
|
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);
|
recyclerView.scrollToPosition(0);
|
||||||
recyclerAdapter.clear();
|
recyclerAdapter.clear();
|
||||||
recyclerAdapter.addAll(PasswordRepository.getPasswords(item.getFile(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
|
recyclerAdapter.addAll(
|
||||||
|
PasswordRepository.getPasswords(
|
||||||
|
item.getFile(),
|
||||||
|
PasswordRepository.getRepositoryDirectory(context),
|
||||||
|
getSortOrder()));
|
||||||
|
|
||||||
((AppCompatActivity) requireActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
((AppCompatActivity) requireActivity())
|
||||||
} else {
|
.getSupportActionBar()
|
||||||
if (getArguments().getBoolean("matchWith", false)) {
|
.setDisplayHomeAsUpEnabled(true);
|
||||||
((PasswordStore) requireActivity()).matchPasswordWithApp(item);
|
} else {
|
||||||
} else {
|
if (getArguments().getBoolean("matchWith", false)) {
|
||||||
((PasswordStore) requireActivity()).decryptPassword(item);
|
((PasswordStore) requireActivity()).matchPasswordWithApp(item);
|
||||||
}
|
} else {
|
||||||
}
|
((PasswordStore) requireActivity()).decryptPassword(item);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
throw new ClassCastException(context + " must implement OnFragmentInteractionListener");
|
throw new ClassCastException(context + " must implement OnFragmentInteractionListener");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** clears the adapter content and sets it back to the root view */
|
||||||
* clears the adapter content and sets it back to the root view
|
|
||||||
*/
|
|
||||||
public void updateAdapter() {
|
public void updateAdapter() {
|
||||||
passListStack.clear();
|
passListStack.clear();
|
||||||
pathStack.clear();
|
pathStack.clear();
|
||||||
scrollPosition.clear();
|
scrollPosition.clear();
|
||||||
recyclerAdapter.clear();
|
recyclerAdapter.clear();
|
||||||
recyclerAdapter.addAll(PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(requireContext()), getSortOrder()));
|
recyclerAdapter.addAll(
|
||||||
|
PasswordRepository.getPasswords(
|
||||||
|
PasswordRepository.getRepositoryDirectory(requireContext()),
|
||||||
|
getSortOrder()));
|
||||||
|
|
||||||
((AppCompatActivity) requireActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(false);
|
((AppCompatActivity) requireActivity())
|
||||||
|
.getSupportActionBar()
|
||||||
|
.setDisplayHomeAsUpEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** refreshes the adapter with the latest opened category */
|
||||||
* refreshes the adapter with the latest opened category
|
|
||||||
*/
|
|
||||||
public void refreshAdapter() {
|
public void refreshAdapter() {
|
||||||
recyclerAdapter.clear();
|
recyclerAdapter.clear();
|
||||||
recyclerAdapter.addAll(pathStack.isEmpty() ?
|
recyclerAdapter.addAll(
|
||||||
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(requireContext()), getSortOrder()) :
|
pathStack.isEmpty()
|
||||||
PasswordRepository.getPasswords(pathStack.peek(), PasswordRepository.getRepositoryDirectory(requireContext()), getSortOrder()));
|
? PasswordRepository.getPasswords(
|
||||||
|
PasswordRepository.getRepositoryDirectory(requireContext()),
|
||||||
|
getSortOrder())
|
||||||
|
: PasswordRepository.getPasswords(
|
||||||
|
pathStack.peek(),
|
||||||
|
PasswordRepository.getRepositoryDirectory(requireContext()),
|
||||||
|
getSortOrder()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -158,13 +184,19 @@ public class PasswordFragment extends Fragment {
|
||||||
* recursively filters a directory and extract all the matching items
|
* recursively filters a directory and extract all the matching items
|
||||||
*
|
*
|
||||||
* @param filter the filter to apply
|
* @param filter the filter to apply
|
||||||
* @param dir the directory to filter
|
* @param dir the directory to filter
|
||||||
*/
|
*/
|
||||||
private void recursiveFilter(String filter, File dir) {
|
private void recursiveFilter(String filter, File dir) {
|
||||||
// on the root the pathStack is empty
|
// on the root the pathStack is empty
|
||||||
ArrayList<PasswordItem> passwordItems = dir == null ?
|
ArrayList<PasswordItem> passwordItems =
|
||||||
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(requireContext()), getSortOrder()) :
|
dir == null
|
||||||
PasswordRepository.getPasswords(dir, PasswordRepository.getRepositoryDirectory(requireContext()), getSortOrder());
|
? PasswordRepository.getPasswords(
|
||||||
|
PasswordRepository.getRepositoryDirectory(requireContext()),
|
||||||
|
getSortOrder())
|
||||||
|
: PasswordRepository.getPasswords(
|
||||||
|
dir,
|
||||||
|
PasswordRepository.getRepositoryDirectory(requireContext()),
|
||||||
|
getSortOrder());
|
||||||
|
|
||||||
boolean rec = settings.getBoolean("filter_recursively", true);
|
boolean rec = settings.getBoolean("filter_recursively", true);
|
||||||
for (PasswordItem item : passwordItems) {
|
for (PasswordItem item : passwordItems) {
|
||||||
|
@ -181,12 +213,9 @@ public class PasswordFragment extends Fragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Goes back one level back in the path */
|
||||||
* Goes back one level back in the path
|
|
||||||
*/
|
|
||||||
public void popBack() {
|
public void popBack() {
|
||||||
if (passListStack.isEmpty())
|
if (passListStack.isEmpty()) return;
|
||||||
return;
|
|
||||||
|
|
||||||
recyclerView.scrollToPosition(scrollPosition.pop());
|
recyclerView.scrollToPosition(scrollPosition.pop());
|
||||||
recyclerAdapter.clear();
|
recyclerAdapter.clear();
|
||||||
|
@ -200,10 +229,8 @@ public class PasswordFragment extends Fragment {
|
||||||
* @return the current directory
|
* @return the current directory
|
||||||
*/
|
*/
|
||||||
public File getCurrentDir() {
|
public File getCurrentDir() {
|
||||||
if (pathStack.isEmpty())
|
if (pathStack.isEmpty()) return PasswordRepository.getRepositoryDirectory(requireContext());
|
||||||
return PasswordRepository.getRepositoryDirectory(requireContext());
|
else return pathStack.peek();
|
||||||
else
|
|
||||||
return pathStack.peek();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isNotEmpty() {
|
public boolean isNotEmpty() {
|
||||||
|
|
|
@ -13,27 +13,19 @@ import android.widget.Button;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.AppCompatEditText;
|
import androidx.appcompat.widget.AppCompatEditText;
|
||||||
import androidx.appcompat.widget.AppCompatTextView;
|
import androidx.appcompat.widget.AppCompatTextView;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.zeapo.pwdstore.pwgen.PasswordGenerator;
|
import com.zeapo.pwdstore.pwgen.PasswordGenerator;
|
||||||
|
import java.util.ArrayList;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
/** A placeholder fragment containing a simple view. */
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A placeholder fragment containing a simple view.
|
|
||||||
*/
|
|
||||||
public class PasswordGeneratorDialogFragment extends DialogFragment {
|
public class PasswordGeneratorDialogFragment extends DialogFragment {
|
||||||
|
|
||||||
public PasswordGeneratorDialogFragment() {
|
public PasswordGeneratorDialogFragment() {}
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
|
@ -42,13 +34,17 @@ public class PasswordGeneratorDialogFragment extends DialogFragment {
|
||||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext());
|
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext());
|
||||||
final Activity callingActivity = requireActivity();
|
final Activity callingActivity = requireActivity();
|
||||||
LayoutInflater inflater = callingActivity.getLayoutInflater();
|
LayoutInflater inflater = callingActivity.getLayoutInflater();
|
||||||
@SuppressLint("InflateParams") final View view = inflater.inflate(R.layout.fragment_pwgen, null);
|
@SuppressLint("InflateParams")
|
||||||
Typeface monoTypeface = Typeface.createFromAsset(callingActivity.getAssets(), "fonts/sourcecodepro.ttf");
|
final View view = inflater.inflate(R.layout.fragment_pwgen, null);
|
||||||
|
Typeface monoTypeface =
|
||||||
|
Typeface.createFromAsset(callingActivity.getAssets(), "fonts/sourcecodepro.ttf");
|
||||||
|
|
||||||
builder.setView(view);
|
builder.setView(view);
|
||||||
|
|
||||||
SharedPreferences prefs
|
SharedPreferences prefs =
|
||||||
= requireActivity().getApplicationContext().getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE);
|
requireActivity()
|
||||||
|
.getApplicationContext()
|
||||||
|
.getSharedPreferences("PasswordGenerator", Context.MODE_PRIVATE);
|
||||||
|
|
||||||
CheckBox checkBox = view.findViewById(R.id.numerals);
|
CheckBox checkBox = view.findViewById(R.id.numerals);
|
||||||
checkBox.setChecked(!prefs.getBoolean("0", false));
|
checkBox.setChecked(!prefs.getBoolean("0", false));
|
||||||
|
@ -74,38 +70,53 @@ public class PasswordGeneratorDialogFragment extends DialogFragment {
|
||||||
AppCompatTextView passwordText = view.findViewById(R.id.passwordText);
|
AppCompatTextView passwordText = view.findViewById(R.id.passwordText);
|
||||||
passwordText.setTypeface(monoTypeface);
|
passwordText.setTypeface(monoTypeface);
|
||||||
|
|
||||||
builder.setPositiveButton(getResources().getString(R.string.dialog_ok), (dialog, which) -> {
|
builder.setPositiveButton(
|
||||||
EditText edit = callingActivity.findViewById(R.id.crypto_password_edit);
|
getResources().getString(R.string.dialog_ok),
|
||||||
edit.setText(passwordText.getText());
|
(dialog, which) -> {
|
||||||
});
|
EditText edit = callingActivity.findViewById(R.id.crypto_password_edit);
|
||||||
|
edit.setText(passwordText.getText());
|
||||||
|
});
|
||||||
|
|
||||||
builder.setNegativeButton(getResources().getString(R.string.dialog_cancel), (dialog, which) -> {
|
builder.setNegativeButton(
|
||||||
|
getResources().getString(R.string.dialog_cancel), (dialog, which) -> {});
|
||||||
});
|
|
||||||
|
|
||||||
builder.setNeutralButton(getResources().getString(R.string.pwgen_generate), null);
|
builder.setNeutralButton(getResources().getString(R.string.pwgen_generate), null);
|
||||||
|
|
||||||
final AlertDialog ad = builder.setTitle(this.getResources().getString(R.string.pwgen_title)).create();
|
final AlertDialog ad =
|
||||||
ad.setOnShowListener(dialog -> {
|
builder.setTitle(this.getResources().getString(R.string.pwgen_title)).create();
|
||||||
setPreferences();
|
ad.setOnShowListener(
|
||||||
try {
|
dialog -> {
|
||||||
passwordText.setText(PasswordGenerator.generate(requireActivity().getApplicationContext()).get(0));
|
setPreferences();
|
||||||
} catch (PasswordGenerator.PasswordGeneratorExeption e) {
|
try {
|
||||||
Toast.makeText(requireActivity(), e.getMessage(), Toast.LENGTH_SHORT).show();
|
passwordText.setText(
|
||||||
passwordText.setText("");
|
PasswordGenerator.generate(
|
||||||
}
|
requireActivity().getApplicationContext())
|
||||||
|
.get(0));
|
||||||
|
} catch (PasswordGenerator.PasswordGeneratorExeption e) {
|
||||||
|
Toast.makeText(requireActivity(), e.getMessage(), Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
passwordText.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
|
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
|
||||||
b.setOnClickListener(v -> {
|
b.setOnClickListener(
|
||||||
setPreferences();
|
v -> {
|
||||||
try {
|
setPreferences();
|
||||||
passwordText.setText(PasswordGenerator.generate(callingActivity.getApplicationContext()).get(0));
|
try {
|
||||||
} catch (PasswordGenerator.PasswordGeneratorExeption e) {
|
passwordText.setText(
|
||||||
Toast.makeText(requireActivity(), e.getMessage(), Toast.LENGTH_SHORT).show();
|
PasswordGenerator.generate(
|
||||||
passwordText.setText("");
|
callingActivity.getApplicationContext())
|
||||||
}
|
.get(0));
|
||||||
});
|
} catch (PasswordGenerator.PasswordGeneratorExeption e) {
|
||||||
});
|
Toast.makeText(
|
||||||
|
requireActivity(),
|
||||||
|
e.getMessage(),
|
||||||
|
Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
passwordText.setText("");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
return ad;
|
return ad;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,10 +144,10 @@ public class PasswordGeneratorDialogFragment extends DialogFragment {
|
||||||
EditText editText = getDialog().findViewById(R.id.lengthNumber);
|
EditText editText = getDialog().findViewById(R.id.lengthNumber);
|
||||||
try {
|
try {
|
||||||
int length = Integer.valueOf(editText.getText().toString());
|
int length = Integer.valueOf(editText.getText().toString());
|
||||||
PasswordGenerator.setPrefs(requireActivity().getApplicationContext(), preferences, length);
|
PasswordGenerator.setPrefs(
|
||||||
|
requireActivity().getApplicationContext(), preferences, length);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
PasswordGenerator.setPrefs(requireActivity().getApplicationContext(), preferences);
|
PasswordGenerator.setPrefs(requireActivity().getApplicationContext(), preferences);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,6 @@ import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.AppCompatTextView;
|
import androidx.appcompat.widget.AppCompatTextView;
|
||||||
|
@ -28,7 +27,6 @@ import androidx.core.content.ContextCompat;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.zeapo.pwdstore.crypto.PgpActivity;
|
import com.zeapo.pwdstore.crypto.PgpActivity;
|
||||||
|
@ -38,14 +36,6 @@ import com.zeapo.pwdstore.git.GitOperation;
|
||||||
import com.zeapo.pwdstore.utils.PasswordItem;
|
import com.zeapo.pwdstore.utils.PasswordItem;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRecyclerAdapter;
|
import com.zeapo.pwdstore.utils.PasswordRecyclerAdapter;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
import org.apache.commons.io.FilenameUtils;
|
|
||||||
import org.eclipse.jgit.api.Git;
|
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
|
||||||
import org.eclipse.jgit.lib.Repository;
|
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -53,6 +43,12 @@ import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
import org.apache.commons.io.FilenameUtils;
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
|
||||||
public class PasswordStore extends AppCompatActivity {
|
public class PasswordStore extends AppCompatActivity {
|
||||||
|
|
||||||
|
@ -65,10 +61,10 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
public static final int REQUEST_CODE_EDIT = 9916;
|
public static final int REQUEST_CODE_EDIT = 9916;
|
||||||
public static final int REQUEST_CODE_SELECT_FOLDER = 9917;
|
public static final int REQUEST_CODE_SELECT_FOLDER = 9917;
|
||||||
private static final String TAG = PasswordStore.class.getName();
|
private static final String TAG = PasswordStore.class.getName();
|
||||||
private final static int CLONE_REPO_BUTTON = 401;
|
private static final int CLONE_REPO_BUTTON = 401;
|
||||||
private final static int NEW_REPO_BUTTON = 402;
|
private static final int NEW_REPO_BUTTON = 402;
|
||||||
private final static int HOME = 403;
|
private static final int HOME = 403;
|
||||||
private final static int REQUEST_EXTERNAL_STORAGE = 50;
|
private static final int REQUEST_EXTERNAL_STORAGE = 50;
|
||||||
private SharedPreferences settings;
|
private SharedPreferences settings;
|
||||||
private Activity activity;
|
private Activity activity;
|
||||||
private PasswordFragment plist;
|
private PasswordFragment plist;
|
||||||
|
@ -78,16 +74,16 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
|
|
||||||
private static boolean isPrintable(char c) {
|
private static boolean isPrintable(char c) {
|
||||||
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
|
Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
|
||||||
return (!Character.isISOControl(c)) &&
|
return (!Character.isISOControl(c))
|
||||||
block != null &&
|
&& block != null
|
||||||
block != Character.UnicodeBlock.SPECIALS;
|
&& block != Character.UnicodeBlock.SPECIALS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
// open search view on search key, or Ctr+F
|
// open search view on search key, or Ctr+F
|
||||||
if ((keyCode == KeyEvent.KEYCODE_SEARCH ||
|
if ((keyCode == KeyEvent.KEYCODE_SEARCH
|
||||||
keyCode == KeyEvent.KEYCODE_F && event.isCtrlPressed())
|
|| keyCode == KeyEvent.KEYCODE_F && event.isCtrlPressed())
|
||||||
&& !searchItem.isActionViewExpanded()) {
|
&& !searchItem.isActionViewExpanded()) {
|
||||||
searchItem.expandActionView();
|
searchItem.expandActionView();
|
||||||
return true;
|
return true;
|
||||||
|
@ -116,8 +112,11 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
|
|
||||||
// If user opens app with permission granted then revokes and returns,
|
// If user opens app with permission granted then revokes and returns,
|
||||||
// prevent attempt to create password list fragment
|
// prevent attempt to create password list fragment
|
||||||
if (savedInstanceState != null && (!settings.getBoolean("git_external", false)
|
if (savedInstanceState != null
|
||||||
|| ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) {
|
&& (!settings.getBoolean("git_external", false)
|
||||||
|
|| ContextCompat.checkSelfPermission(
|
||||||
|
activity, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
|
!= PackageManager.PERMISSION_GRANTED)) {
|
||||||
savedInstanceState = null;
|
savedInstanceState = null;
|
||||||
}
|
}
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -129,27 +128,39 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
// do not attempt to checkLocalRepository() if no storage permission: immediate crash
|
// do not attempt to checkLocalRepository() if no storage permission: immediate crash
|
||||||
if (settings.getBoolean("git_external", false)) {
|
if (settings.getBoolean("git_external", false)) {
|
||||||
if (ContextCompat.checkSelfPermission(activity,
|
if (ContextCompat.checkSelfPermission(
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE)
|
activity, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
!= PackageManager.PERMISSION_GRANTED) {
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
|
||||||
if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
|
if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||||
Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
activity, Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||||
// TODO: strings.xml
|
// TODO: strings.xml
|
||||||
Snackbar snack = Snackbar.make(findViewById(R.id.main_layout), "The store is on the sdcard but the app does not have permission to access it. Please give permission.",
|
Snackbar snack =
|
||||||
Snackbar.LENGTH_INDEFINITE)
|
Snackbar.make(
|
||||||
.setAction(R.string.dialog_ok, view -> ActivityCompat.requestPermissions(activity,
|
findViewById(R.id.main_layout),
|
||||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
"The store is on the sdcard but the app does not have permission to access it. Please give permission.",
|
||||||
REQUEST_EXTERNAL_STORAGE));
|
Snackbar.LENGTH_INDEFINITE)
|
||||||
|
.setAction(
|
||||||
|
R.string.dialog_ok,
|
||||||
|
view ->
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
activity,
|
||||||
|
new String[] {
|
||||||
|
Manifest.permission
|
||||||
|
.READ_EXTERNAL_STORAGE
|
||||||
|
},
|
||||||
|
REQUEST_EXTERNAL_STORAGE));
|
||||||
snack.show();
|
snack.show();
|
||||||
View view = snack.getView();
|
View view = snack.getView();
|
||||||
AppCompatTextView tv = view.findViewById(com.google.android.material.R.id.snackbar_text);
|
AppCompatTextView tv =
|
||||||
|
view.findViewById(com.google.android.material.R.id.snackbar_text);
|
||||||
tv.setTextColor(Color.WHITE);
|
tv.setTextColor(Color.WHITE);
|
||||||
tv.setMaxLines(10);
|
tv.setMaxLines(10);
|
||||||
} else {
|
} else {
|
||||||
// No explanation needed, we can request the permission.
|
// No explanation needed, we can request the permission.
|
||||||
ActivityCompat.requestPermissions(activity,
|
ActivityCompat.requestPermissions(
|
||||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
activity,
|
||||||
|
new String[] {Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||||
REQUEST_EXTERNAL_STORAGE);
|
REQUEST_EXTERNAL_STORAGE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -162,11 +173,11 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(
|
||||||
|
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
// If request is cancelled, the result arrays are empty.
|
// If request is cancelled, the result arrays are empty.
|
||||||
if (requestCode == REQUEST_EXTERNAL_STORAGE) {
|
if (requestCode == REQUEST_EXTERNAL_STORAGE) {
|
||||||
if (grantResults.length > 0
|
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
checkLocalRepository();
|
checkLocalRepository();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,33 +190,35 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
searchItem = menu.findItem(R.id.action_search);
|
searchItem = menu.findItem(R.id.action_search);
|
||||||
searchView = (SearchView) searchItem.getActionView();
|
searchView = (SearchView) searchItem.getActionView();
|
||||||
|
|
||||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
searchView.setOnQueryTextListener(
|
||||||
@Override
|
new SearchView.OnQueryTextListener() {
|
||||||
public boolean onQueryTextSubmit(String s) {
|
@Override
|
||||||
return true;
|
public boolean onQueryTextSubmit(String s) {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextChange(String s) {
|
public boolean onQueryTextChange(String s) {
|
||||||
filterListAdapter(s);
|
filterListAdapter(s);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// When using the support library, the setOnActionExpandListener() method is
|
// When using the support library, the setOnActionExpandListener() method is
|
||||||
// static and accepts the MenuItem object as an argument
|
// static and accepts the MenuItem object as an argument
|
||||||
searchItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
searchItem.setOnActionExpandListener(
|
||||||
@Override
|
new MenuItem.OnActionExpandListener() {
|
||||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
@Override
|
||||||
refreshListAdapter();
|
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||||
return true;
|
refreshListAdapter();
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return super.onCreateOptionsMenu(menu);
|
return super.onCreateOptionsMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,9 +230,10 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
int id = item.getItemId();
|
int id = item.getItemId();
|
||||||
Intent intent;
|
Intent intent;
|
||||||
|
|
||||||
final MaterialAlertDialogBuilder initBefore = new MaterialAlertDialogBuilder(this)
|
final MaterialAlertDialogBuilder initBefore =
|
||||||
.setMessage(this.getResources().getString(R.string.creation_dialog_text))
|
new MaterialAlertDialogBuilder(this)
|
||||||
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), null);
|
.setMessage(this.getResources().getString(R.string.creation_dialog_text))
|
||||||
|
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), null);
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
case R.id.user_pref:
|
case R.id.user_pref:
|
||||||
|
@ -306,8 +320,7 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
|
|
||||||
final File localDir = PasswordRepository.getRepositoryDirectory(getApplicationContext());
|
final File localDir = PasswordRepository.getRepositoryDirectory(getApplicationContext());
|
||||||
try {
|
try {
|
||||||
if (!localDir.mkdir())
|
if (!localDir.mkdir()) throw new IllegalStateException("Failed to create directory!");
|
||||||
throw new IllegalStateException("Failed to create directory!");
|
|
||||||
PasswordRepository.createRepository(localDir);
|
PasswordRepository.createRepository(localDir);
|
||||||
if (new File(localDir.getAbsolutePath() + "/.gpg-id").createNewFile()) {
|
if (new File(localDir.getAbsolutePath() + "/.gpg-id").createNewFile()) {
|
||||||
settings.edit().putBoolean("repository_initialized", true).apply();
|
settings.edit().putBoolean("repository_initialized", true).apply();
|
||||||
|
@ -329,9 +342,13 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
if (settings.getBoolean("git_external", false) && externalRepoPath != null) {
|
if (settings.getBoolean("git_external", false) && externalRepoPath != null) {
|
||||||
File dir = new File(externalRepoPath);
|
File dir = new File(externalRepoPath);
|
||||||
|
|
||||||
if (dir.exists() &&
|
if (dir.exists()
|
||||||
dir.isDirectory() &&
|
&& dir.isDirectory()
|
||||||
!PasswordRepository.getPasswords(dir, PasswordRepository.getRepositoryDirectory(this), getSortOrder()).isEmpty()) {
|
&& !PasswordRepository.getPasswords(
|
||||||
|
dir,
|
||||||
|
PasswordRepository.getRepositoryDirectory(this),
|
||||||
|
getSortOrder())
|
||||||
|
.isEmpty()) {
|
||||||
|
|
||||||
PasswordRepository.closeRepository();
|
PasswordRepository.closeRepository();
|
||||||
checkLocalRepository();
|
checkLocalRepository();
|
||||||
|
@ -344,11 +361,14 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
if (keyIds.isEmpty())
|
if (keyIds.isEmpty())
|
||||||
new MaterialAlertDialogBuilder(this)
|
new MaterialAlertDialogBuilder(this)
|
||||||
.setMessage(this.getResources().getString(R.string.key_dialog_text))
|
.setMessage(this.getResources().getString(R.string.key_dialog_text))
|
||||||
.setPositiveButton(this.getResources().getString(R.string.dialog_positive), (dialogInterface, i) -> {
|
.setPositiveButton(
|
||||||
Intent intent = new Intent(activity, UserPreference.class);
|
this.getResources().getString(R.string.dialog_positive),
|
||||||
startActivityForResult(intent, GitActivity.REQUEST_INIT);
|
(dialogInterface, i) -> {
|
||||||
})
|
Intent intent = new Intent(activity, UserPreference.class);
|
||||||
.setNegativeButton(this.getResources().getString(R.string.dialog_negative), null)
|
startActivityForResult(intent, GitActivity.REQUEST_INIT);
|
||||||
|
})
|
||||||
|
.setNegativeButton(
|
||||||
|
this.getResources().getString(R.string.dialog_negative), null)
|
||||||
.show();
|
.show();
|
||||||
|
|
||||||
createRepository();
|
createRepository();
|
||||||
|
@ -361,7 +381,8 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
intent.putExtra("operation", "git_external");
|
intent.putExtra("operation", "git_external");
|
||||||
startActivityForResult(intent, HOME);
|
startActivityForResult(intent, HOME);
|
||||||
} else {
|
} else {
|
||||||
checkLocalRepository(PasswordRepository.getRepositoryDirectory(getApplicationContext()));
|
checkLocalRepository(
|
||||||
|
PasswordRepository.getRepositoryDirectory(getApplicationContext()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,12 +392,16 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
if (localDir != null && settings.getBoolean("repository_initialized", false)) {
|
if (localDir != null && settings.getBoolean("repository_initialized", false)) {
|
||||||
Log.d(TAG, "Check, dir: " + localDir.getAbsolutePath());
|
Log.d(TAG, "Check, dir: " + localDir.getAbsolutePath());
|
||||||
// do not push the fragment if we already have it
|
// do not push the fragment if we already have it
|
||||||
if (fragmentManager.findFragmentByTag("PasswordsList") == null || settings.getBoolean("repo_changed", false)) {
|
if (fragmentManager.findFragmentByTag("PasswordsList") == null
|
||||||
|
|| settings.getBoolean("repo_changed", false)) {
|
||||||
settings.edit().putBoolean("repo_changed", false).apply();
|
settings.edit().putBoolean("repo_changed", false).apply();
|
||||||
|
|
||||||
plist = new PasswordFragment();
|
plist = new PasswordFragment();
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putString("Path", PasswordRepository.getRepositoryDirectory(getApplicationContext()).getAbsolutePath());
|
args.putString(
|
||||||
|
"Path",
|
||||||
|
PasswordRepository.getRepositoryDirectory(getApplicationContext())
|
||||||
|
.getAbsolutePath());
|
||||||
|
|
||||||
// if the activity was started from the autofill settings, the
|
// if the activity was started from the autofill settings, the
|
||||||
// intent is to match a clicked pwd with app. pass this to fragment
|
// intent is to match a clicked pwd with app. pass this to fragment
|
||||||
|
@ -405,7 +430,6 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBackPressed() {
|
public void onBackPressed() {
|
||||||
if ((null != plist) && plist.isNotEmpty()) {
|
if ((null != plist) && plist.isNotEmpty()) {
|
||||||
|
@ -433,16 +457,13 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
Git git = new Git(repository);
|
Git git = new Git(repository);
|
||||||
String relativePath = getRelativePath(fullPath, repoPath.getAbsolutePath())
|
String relativePath =
|
||||||
.substring(1); // Removes leading '/'
|
getRelativePath(fullPath, repoPath.getAbsolutePath())
|
||||||
|
.substring(1); // Removes leading '/'
|
||||||
|
|
||||||
Iterator<RevCommit> iterator;
|
Iterator<RevCommit> iterator;
|
||||||
try {
|
try {
|
||||||
iterator = git
|
iterator = git.log().addPath(relativePath).call().iterator();
|
||||||
.log()
|
|
||||||
.addPath(relativePath)
|
|
||||||
.call()
|
|
||||||
.iterator();
|
|
||||||
} catch (GitAPIException e) {
|
} catch (GitAPIException e) {
|
||||||
Log.e(TAG, "getLastChangedTimestamp: GITAPIException", e);
|
Log.e(TAG, "getLastChangedTimestamp: GITAPIException", e);
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -462,23 +483,29 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
for (Intent intent : new Intent[] {decryptIntent, authDecryptIntent}) {
|
for (Intent intent : new Intent[] {decryptIntent, authDecryptIntent}) {
|
||||||
intent.putExtra("NAME", item.toString());
|
intent.putExtra("NAME", item.toString());
|
||||||
intent.putExtra("FILE_PATH", item.getFile().getAbsolutePath());
|
intent.putExtra("FILE_PATH", item.getFile().getAbsolutePath());
|
||||||
intent.putExtra("REPO_PATH", PasswordRepository.getRepositoryDirectory(getApplicationContext()).getAbsolutePath());
|
intent.putExtra(
|
||||||
intent.putExtra("LAST_CHANGED_TIMESTAMP", getLastChangedTimestamp(item.getFile().getAbsolutePath()));
|
"REPO_PATH",
|
||||||
|
PasswordRepository.getRepositoryDirectory(getApplicationContext())
|
||||||
|
.getAbsolutePath());
|
||||||
|
intent.putExtra(
|
||||||
|
"LAST_CHANGED_TIMESTAMP",
|
||||||
|
getLastChangedTimestamp(item.getFile().getAbsolutePath()));
|
||||||
intent.putExtra("OPERATION", "DECRYPT");
|
intent.putExtra("OPERATION", "DECRYPT");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds shortcut
|
// Adds shortcut
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||||
ShortcutInfo shortcut = new ShortcutInfo.Builder(this, item.getFullPathToParent())
|
ShortcutInfo shortcut =
|
||||||
.setShortLabel(item.toString())
|
new ShortcutInfo.Builder(this, item.getFullPathToParent())
|
||||||
.setLongLabel(item.getFullPathToParent() + item.toString())
|
.setShortLabel(item.toString())
|
||||||
.setIcon(Icon.createWithResource(this, R.mipmap.ic_launcher))
|
.setLongLabel(item.getFullPathToParent() + item.toString())
|
||||||
.setIntent(authDecryptIntent.setAction("DECRYPT_PASS")) // Needs action
|
.setIcon(Icon.createWithResource(this, R.mipmap.ic_launcher))
|
||||||
.build();
|
.setIntent(authDecryptIntent.setAction("DECRYPT_PASS")) // Needs action
|
||||||
|
.build();
|
||||||
List<ShortcutInfo> shortcuts = shortcutManager.getDynamicShortcuts();
|
List<ShortcutInfo> shortcuts = shortcutManager.getDynamicShortcuts();
|
||||||
|
|
||||||
if (shortcuts.size() >= shortcutManager.getMaxShortcutCountPerActivity() &&
|
if (shortcuts.size() >= shortcutManager.getMaxShortcutCountPerActivity()
|
||||||
shortcuts.size() > 0) {
|
&& shortcuts.size() > 0) {
|
||||||
shortcuts.remove(shortcuts.size() - 1);
|
shortcuts.remove(shortcuts.size() - 1);
|
||||||
shortcuts.add(0, shortcut);
|
shortcuts.add(0, shortcut);
|
||||||
shortcutManager.setDynamicShortcuts(shortcuts);
|
shortcutManager.setDynamicShortcuts(shortcuts);
|
||||||
|
@ -494,7 +521,10 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
intent.putExtra("NAME", item.toString());
|
intent.putExtra("NAME", item.toString());
|
||||||
intent.putExtra("FILE_PATH", item.getFile().getAbsolutePath());
|
intent.putExtra("FILE_PATH", item.getFile().getAbsolutePath());
|
||||||
intent.putExtra("PARENT_PATH", getCurrentDir().getAbsolutePath());
|
intent.putExtra("PARENT_PATH", getCurrentDir().getAbsolutePath());
|
||||||
intent.putExtra("REPO_PATH", PasswordRepository.getRepositoryDirectory(getApplicationContext()).getAbsolutePath());
|
intent.putExtra(
|
||||||
|
"REPO_PATH",
|
||||||
|
PasswordRepository.getRepositoryDirectory(getApplicationContext())
|
||||||
|
.getAbsolutePath());
|
||||||
intent.putExtra("OPERATION", "EDIT");
|
intent.putExtra("OPERATION", "EDIT");
|
||||||
startActivityForResult(intent, REQUEST_CODE_EDIT);
|
startActivityForResult(intent, REQUEST_CODE_EDIT);
|
||||||
}
|
}
|
||||||
|
@ -503,8 +533,10 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
if (!PasswordRepository.isInitialized()) {
|
if (!PasswordRepository.isInitialized()) {
|
||||||
new MaterialAlertDialogBuilder(this)
|
new MaterialAlertDialogBuilder(this)
|
||||||
.setMessage(this.getResources().getString(R.string.creation_dialog_text))
|
.setMessage(this.getResources().getString(R.string.creation_dialog_text))
|
||||||
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
|
.setPositiveButton(
|
||||||
}).show();
|
this.getResources().getString(R.string.dialog_ok),
|
||||||
|
(dialogInterface, i) -> {})
|
||||||
|
.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -512,10 +544,13 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
new MaterialAlertDialogBuilder(this)
|
new MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(this.getResources().getString(R.string.no_key_selected_dialog_title))
|
.setTitle(this.getResources().getString(R.string.no_key_selected_dialog_title))
|
||||||
.setMessage(this.getResources().getString(R.string.no_key_selected_dialog_text))
|
.setMessage(this.getResources().getString(R.string.no_key_selected_dialog_text))
|
||||||
.setPositiveButton(this.getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
|
.setPositiveButton(
|
||||||
Intent intent = new Intent(activity, UserPreference.class);
|
this.getResources().getString(R.string.dialog_ok),
|
||||||
startActivity(intent);
|
(dialogInterface, i) -> {
|
||||||
}).show();
|
Intent intent = new Intent(activity, UserPreference.class);
|
||||||
|
startActivity(intent);
|
||||||
|
})
|
||||||
|
.show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -524,13 +559,17 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
|
|
||||||
Intent intent = new Intent(this, PgpActivity.class);
|
Intent intent = new Intent(this, PgpActivity.class);
|
||||||
intent.putExtra("FILE_PATH", getCurrentDir().getAbsolutePath());
|
intent.putExtra("FILE_PATH", getCurrentDir().getAbsolutePath());
|
||||||
intent.putExtra("REPO_PATH", PasswordRepository.getRepositoryDirectory(getApplicationContext()).getAbsolutePath());
|
intent.putExtra(
|
||||||
|
"REPO_PATH",
|
||||||
|
PasswordRepository.getRepositoryDirectory(getApplicationContext())
|
||||||
|
.getAbsolutePath());
|
||||||
intent.putExtra("OPERATION", "ENCRYPT");
|
intent.putExtra("OPERATION", "ENCRYPT");
|
||||||
startActivityForResult(intent, REQUEST_CODE_ENCRYPT);
|
startActivityForResult(intent, REQUEST_CODE_ENCRYPT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// deletes passwords in order from top to bottom
|
// deletes passwords in order from top to bottom
|
||||||
public void deletePasswords(final PasswordRecyclerAdapter adapter, final Set<Integer> selectedItems) {
|
public void deletePasswords(
|
||||||
|
final PasswordRecyclerAdapter adapter, final Set<Integer> selectedItems) {
|
||||||
final Iterator it = selectedItems.iterator();
|
final Iterator it = selectedItems.iterator();
|
||||||
if (!it.hasNext()) {
|
if (!it.hasNext()) {
|
||||||
return;
|
return;
|
||||||
|
@ -538,21 +577,29 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
final int position = (int) it.next();
|
final int position = (int) it.next();
|
||||||
final PasswordItem item = adapter.getValues().get(position);
|
final PasswordItem item = adapter.getValues().get(position);
|
||||||
new MaterialAlertDialogBuilder(this)
|
new MaterialAlertDialogBuilder(this)
|
||||||
.setMessage(getResources().getString(R.string.delete_dialog_text, item.getLongName()))
|
.setMessage(
|
||||||
.setPositiveButton(getResources().getString(R.string.dialog_yes), (dialogInterface, i) -> {
|
getResources().getString(R.string.delete_dialog_text, item.getLongName()))
|
||||||
item.getFile().delete();
|
.setPositiveButton(
|
||||||
adapter.remove(position);
|
getResources().getString(R.string.dialog_yes),
|
||||||
it.remove();
|
(dialogInterface, i) -> {
|
||||||
adapter.updateSelectedItems(position, selectedItems);
|
item.getFile().delete();
|
||||||
|
adapter.remove(position);
|
||||||
|
it.remove();
|
||||||
|
adapter.updateSelectedItems(position, selectedItems);
|
||||||
|
|
||||||
commitChange(getResources().getString(R.string.git_commit_remove_text,
|
commitChange(
|
||||||
item.getLongName()));
|
getResources()
|
||||||
deletePasswords(adapter, selectedItems);
|
.getString(
|
||||||
})
|
R.string.git_commit_remove_text,
|
||||||
.setNegativeButton(this.getResources().getString(R.string.dialog_no), (dialogInterface, i) -> {
|
item.getLongName()));
|
||||||
it.remove();
|
deletePasswords(adapter, selectedItems);
|
||||||
deletePasswords(adapter, selectedItems);
|
})
|
||||||
})
|
.setNegativeButton(
|
||||||
|
this.getResources().getString(R.string.dialog_no),
|
||||||
|
(dialogInterface, i) -> {
|
||||||
|
it.remove();
|
||||||
|
deletePasswords(adapter, selectedItems);
|
||||||
|
})
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -567,18 +614,14 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
startActivityForResult(intent, REQUEST_CODE_SELECT_FOLDER);
|
startActivityForResult(intent, REQUEST_CODE_SELECT_FOLDER);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** clears adapter's content and updates it with a fresh list of passwords from the root */
|
||||||
* clears adapter's content and updates it with a fresh list of passwords from the root
|
|
||||||
*/
|
|
||||||
public void updateListAdapter() {
|
public void updateListAdapter() {
|
||||||
if ((null != plist)) {
|
if ((null != plist)) {
|
||||||
plist.updateAdapter();
|
plist.updateAdapter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Updates the adapter with the current view of passwords */
|
||||||
* Updates the adapter with the current view of passwords
|
|
||||||
*/
|
|
||||||
private void refreshListAdapter() {
|
private void refreshListAdapter() {
|
||||||
if ((null != plist)) {
|
if ((null != plist)) {
|
||||||
plist.refreshAdapter();
|
plist.refreshAdapter();
|
||||||
|
@ -607,14 +650,12 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
GitAsyncTask tasks = new GitAsyncTask(activity, false, true, this);
|
GitAsyncTask tasks = new GitAsyncTask(activity, false, true, this);
|
||||||
tasks.execute(
|
tasks.execute(
|
||||||
git.add().addFilepattern("."),
|
git.add().addFilepattern("."),
|
||||||
git.commit().setAll(true).setMessage(message)
|
git.commit().setAll(true).setMessage(message));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}.execute();
|
}.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode,
|
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
Intent data) {
|
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
case GitActivity.REQUEST_CLONE:
|
case GitActivity.REQUEST_CLONE:
|
||||||
|
@ -622,26 +663,39 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
settings.edit().putBoolean("repository_initialized", true).apply();
|
settings.edit().putBoolean("repository_initialized", true).apply();
|
||||||
break;
|
break;
|
||||||
case REQUEST_CODE_DECRYPT_AND_VERIFY:
|
case REQUEST_CODE_DECRYPT_AND_VERIFY:
|
||||||
// if went from decrypt->edit and user saved changes or HOTP counter was incremented, we need to commitChange
|
// if went from decrypt->edit and user saved changes or HOTP counter was
|
||||||
|
// incremented, we need to commitChange
|
||||||
if (data != null && data.getBooleanExtra("needCommit", false)) {
|
if (data != null && data.getBooleanExtra("needCommit", false)) {
|
||||||
if (data.getStringExtra("OPERATION").equals("EDIT")) {
|
if (data.getStringExtra("OPERATION").equals("EDIT")) {
|
||||||
commitChange(this.getResources().getString(R.string.git_commit_edit_text,
|
commitChange(
|
||||||
data.getExtras().getString("LONG_NAME")));
|
this.getResources()
|
||||||
|
.getString(
|
||||||
|
R.string.git_commit_edit_text,
|
||||||
|
data.getExtras().getString("LONG_NAME")));
|
||||||
} else {
|
} else {
|
||||||
commitChange(this.getResources().getString(R.string.git_commit_increment_text,
|
commitChange(
|
||||||
data.getExtras().getString("LONG_NAME")));
|
this.getResources()
|
||||||
|
.getString(
|
||||||
|
R.string.git_commit_increment_text,
|
||||||
|
data.getExtras().getString("LONG_NAME")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refreshListAdapter();
|
refreshListAdapter();
|
||||||
break;
|
break;
|
||||||
case REQUEST_CODE_ENCRYPT:
|
case REQUEST_CODE_ENCRYPT:
|
||||||
commitChange(this.getResources().getString(R.string.git_commit_add_text,
|
commitChange(
|
||||||
data.getExtras().getString("LONG_NAME")));
|
this.getResources()
|
||||||
|
.getString(
|
||||||
|
R.string.git_commit_add_text,
|
||||||
|
data.getExtras().getString("LONG_NAME")));
|
||||||
refreshListAdapter();
|
refreshListAdapter();
|
||||||
break;
|
break;
|
||||||
case REQUEST_CODE_EDIT:
|
case REQUEST_CODE_EDIT:
|
||||||
commitChange(this.getResources().getString(R.string.git_commit_edit_text,
|
commitChange(
|
||||||
data.getExtras().getString("LONG_NAME")));
|
this.getResources()
|
||||||
|
.getString(
|
||||||
|
R.string.git_commit_edit_text,
|
||||||
|
data.getExtras().getString("LONG_NAME")));
|
||||||
refreshListAdapter();
|
refreshListAdapter();
|
||||||
break;
|
break;
|
||||||
case GitActivity.REQUEST_INIT:
|
case GitActivity.REQUEST_INIT:
|
||||||
|
@ -657,15 +711,20 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
break;
|
break;
|
||||||
case CLONE_REPO_BUTTON:
|
case CLONE_REPO_BUTTON:
|
||||||
// duplicate code
|
// duplicate code
|
||||||
if (settings.getBoolean("git_external", false) && settings.getString("git_external_repo", null) != null) {
|
if (settings.getBoolean("git_external", false)
|
||||||
|
&& settings.getString("git_external_repo", null) != null) {
|
||||||
String externalRepoPath = settings.getString("git_external_repo", null);
|
String externalRepoPath = settings.getString("git_external_repo", null);
|
||||||
File dir = externalRepoPath != null ? new File(externalRepoPath) : null;
|
File dir = externalRepoPath != null ? new File(externalRepoPath) : null;
|
||||||
|
|
||||||
if (dir != null &&
|
if (dir != null
|
||||||
dir.exists() &&
|
&& dir.exists()
|
||||||
dir.isDirectory() &&
|
&& dir.isDirectory()
|
||||||
!FileUtils.listFiles(dir, null, true).isEmpty() &&
|
&& !FileUtils.listFiles(dir, null, true).isEmpty()
|
||||||
!PasswordRepository.getPasswords(dir, PasswordRepository.getRepositoryDirectory(this), getSortOrder()).isEmpty()) {
|
&& !PasswordRepository.getPasswords(
|
||||||
|
dir,
|
||||||
|
PasswordRepository.getRepositoryDirectory(this),
|
||||||
|
getSortOrder())
|
||||||
|
.isEmpty()) {
|
||||||
PasswordRepository.closeRepository();
|
PasswordRepository.closeRepository();
|
||||||
checkLocalRepository();
|
checkLocalRepository();
|
||||||
return; // if not empty, just show me the passwords!
|
return; // if not empty, just show me the passwords!
|
||||||
|
@ -676,7 +735,9 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
|
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
|
||||||
break;
|
break;
|
||||||
case REQUEST_CODE_SELECT_FOLDER:
|
case REQUEST_CODE_SELECT_FOLDER:
|
||||||
Log.d(TAG, "Moving passwords to " + data.getStringExtra("SELECTED_FOLDER_PATH"));
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Moving passwords to " + data.getStringExtra("SELECTED_FOLDER_PATH"));
|
||||||
Log.d(TAG, TextUtils.join(", ", data.getStringArrayListExtra("Files")));
|
Log.d(TAG, TextUtils.join(", ", data.getStringArrayListExtra("Files")));
|
||||||
File target = new File(data.getStringExtra("SELECTED_FOLDER_PATH"));
|
File target = new File(data.getStringExtra("SELECTED_FOLDER_PATH"));
|
||||||
if (!target.isDirectory()) {
|
if (!target.isDirectory()) {
|
||||||
|
@ -684,9 +745,9 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
String repositoryPath = PasswordRepository
|
String repositoryPath =
|
||||||
.getRepositoryDirectory(getApplicationContext())
|
PasswordRepository.getRepositoryDirectory(getApplicationContext())
|
||||||
.getAbsolutePath();
|
.getAbsolutePath();
|
||||||
|
|
||||||
// TODO move this to an async task
|
// TODO move this to an async task
|
||||||
for (String fileString : data.getStringArrayListExtra("Files")) {
|
for (String fileString : data.getStringArrayListExtra("Files")) {
|
||||||
|
@ -696,23 +757,33 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
File destinationFile = new File(target.getAbsolutePath() + "/" + source.getName());
|
File destinationFile =
|
||||||
|
new File(target.getAbsolutePath() + "/" + source.getName());
|
||||||
|
|
||||||
String basename = FilenameUtils.getBaseName(source.getAbsolutePath());
|
String basename = FilenameUtils.getBaseName(source.getAbsolutePath());
|
||||||
|
|
||||||
String sourceLongName = PgpActivity.getLongName(source.getParent(),
|
String sourceLongName =
|
||||||
repositoryPath, basename);
|
PgpActivity.getLongName(
|
||||||
|
source.getParent(), repositoryPath, basename);
|
||||||
|
|
||||||
String destinationLongName = PgpActivity.getLongName(target.getAbsolutePath(),
|
String destinationLongName =
|
||||||
repositoryPath, basename);
|
PgpActivity.getLongName(
|
||||||
|
target.getAbsolutePath(), repositoryPath, basename);
|
||||||
|
|
||||||
if (destinationFile.exists()) {
|
if (destinationFile.exists()) {
|
||||||
Log.e(TAG, "Trying to move a file that already exists.");
|
Log.e(TAG, "Trying to move a file that already exists.");
|
||||||
// TODO: Add option to cancel overwrite. Will be easier once this is an async task.
|
// TODO: Add option to cancel overwrite. Will be easier once this is an
|
||||||
|
// async task.
|
||||||
new MaterialAlertDialogBuilder(this)
|
new MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(getResources().getString(R.string.password_exists_title))
|
.setTitle(
|
||||||
.setMessage(getResources().getString(R.string.password_exists_message,
|
getResources()
|
||||||
destinationLongName, sourceLongName))
|
.getString(R.string.password_exists_title))
|
||||||
|
.setMessage(
|
||||||
|
getResources()
|
||||||
|
.getString(
|
||||||
|
R.string.password_exists_message,
|
||||||
|
destinationLongName,
|
||||||
|
sourceLongName))
|
||||||
.setPositiveButton("Okay", null)
|
.setPositiveButton("Okay", null)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
@ -721,10 +792,12 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
// TODO this should show a warning to the user
|
// TODO this should show a warning to the user
|
||||||
Log.e(TAG, "Something went wrong while moving.");
|
Log.e(TAG, "Something went wrong while moving.");
|
||||||
} else {
|
} else {
|
||||||
commitChange(this.getResources()
|
commitChange(
|
||||||
.getString(R.string.git_commit_move_text,
|
this.getResources()
|
||||||
sourceLongName,
|
.getString(
|
||||||
destinationLongName));
|
R.string.git_commit_move_text,
|
||||||
|
sourceLongName,
|
||||||
|
destinationLongName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateListAdapter();
|
updateListAdapter();
|
||||||
|
@ -743,63 +816,94 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
new MaterialAlertDialogBuilder(this)
|
new MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(this.getResources().getString(R.string.location_dialog_title))
|
.setTitle(this.getResources().getString(R.string.location_dialog_title))
|
||||||
.setMessage(this.getResources().getString(R.string.location_dialog_text))
|
.setMessage(this.getResources().getString(R.string.location_dialog_text))
|
||||||
.setPositiveButton(this.getResources().getString(R.string.location_hidden), (dialog, whichButton) -> {
|
.setPositiveButton(
|
||||||
settings.edit().putBoolean("git_external", false).apply();
|
this.getResources().getString(R.string.location_hidden),
|
||||||
|
(dialog, whichButton) -> {
|
||||||
|
settings.edit().putBoolean("git_external", false).apply();
|
||||||
|
|
||||||
switch (operation) {
|
switch (operation) {
|
||||||
case NEW_REPO_BUTTON:
|
case NEW_REPO_BUTTON:
|
||||||
initializeRepositoryInfo();
|
initializeRepositoryInfo();
|
||||||
break;
|
break;
|
||||||
case CLONE_REPO_BUTTON:
|
case CLONE_REPO_BUTTON:
|
||||||
PasswordRepository.initialize(PasswordStore.this);
|
PasswordRepository.initialize(PasswordStore.this);
|
||||||
|
|
||||||
Intent intent = new Intent(activity, GitActivity.class);
|
Intent intent = new Intent(activity, GitActivity.class);
|
||||||
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
|
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
|
||||||
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
|
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton(this.getResources().getString(R.string.location_sdcard), (dialog, whichButton) -> {
|
.setNegativeButton(
|
||||||
settings.edit().putBoolean("git_external", true).apply();
|
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) {
|
if (externalRepo == null) {
|
||||||
Intent intent = new Intent(activity, UserPreference.class);
|
Intent intent = new Intent(activity, UserPreference.class);
|
||||||
intent.putExtra("operation", "git_external");
|
intent.putExtra("operation", "git_external");
|
||||||
startActivityForResult(intent, operation);
|
startActivityForResult(intent, operation);
|
||||||
} else {
|
} else {
|
||||||
new MaterialAlertDialogBuilder(activity)
|
new MaterialAlertDialogBuilder(activity)
|
||||||
.setTitle(getResources().getString(R.string.directory_selected_title))
|
.setTitle(
|
||||||
.setMessage(getResources().getString(R.string.directory_selected_message, externalRepo))
|
getResources()
|
||||||
.setPositiveButton(getResources().getString(R.string.use), (dialog1, which) -> {
|
.getString(
|
||||||
switch (operation) {
|
R.string.directory_selected_title))
|
||||||
case NEW_REPO_BUTTON:
|
.setMessage(
|
||||||
initializeRepositoryInfo();
|
getResources()
|
||||||
break;
|
.getString(
|
||||||
case CLONE_REPO_BUTTON:
|
R.string.directory_selected_message,
|
||||||
PasswordRepository.initialize(PasswordStore.this);
|
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 intent =
|
||||||
intent.putExtra("Operation", GitActivity.REQUEST_CLONE);
|
new Intent(
|
||||||
startActivityForResult(intent, GitActivity.REQUEST_CLONE);
|
activity,
|
||||||
break;
|
GitActivity.class);
|
||||||
}
|
intent.putExtra(
|
||||||
})
|
"Operation",
|
||||||
.setNegativeButton(getResources().getString(R.string.change), (dialog12, which) -> {
|
GitActivity.REQUEST_CLONE);
|
||||||
Intent intent = new Intent(activity, UserPreference.class);
|
startActivityForResult(
|
||||||
intent.putExtra("operation", "git_external");
|
intent,
|
||||||
startActivityForResult(intent, operation);
|
GitActivity.REQUEST_CLONE);
|
||||||
}).show();
|
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();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void matchPasswordWithApp(PasswordItem item) {
|
public void matchPasswordWithApp(PasswordItem item) {
|
||||||
String path = item.getFile().getAbsolutePath()
|
String path =
|
||||||
.replace(PasswordRepository.getRepositoryDirectory(getApplicationContext()) + "/", "")
|
item.getFile()
|
||||||
.replace(".gpg", "");
|
.getAbsolutePath()
|
||||||
|
.replace(
|
||||||
|
PasswordRepository.getRepositoryDirectory(getApplicationContext())
|
||||||
|
+ "/",
|
||||||
|
"")
|
||||||
|
.replace(".gpg", "");
|
||||||
Intent data = new Intent();
|
Intent data = new Intent();
|
||||||
data.putExtra("path", path);
|
data.putExtra("path", path);
|
||||||
setResult(RESULT_OK, data);
|
setResult(RESULT_OK, data);
|
||||||
|
|
|
@ -21,7 +21,6 @@ class SelectFolderActivity : AppCompatActivity() {
|
||||||
val fragmentManager = supportFragmentManager
|
val fragmentManager = supportFragmentManager
|
||||||
val fragmentTransaction = fragmentManager.beginTransaction()
|
val fragmentTransaction = fragmentManager.beginTransaction()
|
||||||
|
|
||||||
|
|
||||||
passwordList = SelectFolderFragment()
|
passwordList = SelectFolderFragment()
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
args.putString("Path", PasswordRepository.getRepositoryDirectory(applicationContext)?.absolutePath)
|
args.putString("Path", PasswordRepository.getRepositoryDirectory(applicationContext)?.absolutePath)
|
||||||
|
@ -59,4 +58,4 @@ class SelectFolderActivity : AppCompatActivity() {
|
||||||
setResult(Activity.RESULT_OK, intent)
|
setResult(Activity.RESULT_OK, intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
@ -13,21 +12,20 @@ import androidx.preference.PreferenceManager;
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.zeapo.pwdstore.utils.FolderRecyclerAdapter;
|
import com.zeapo.pwdstore.utils.FolderRecyclerAdapter;
|
||||||
import com.zeapo.pwdstore.utils.PasswordItem;
|
import com.zeapo.pwdstore.utils.PasswordItem;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment representing a list of Items.
|
* A fragment representing a list of Items.
|
||||||
* <p/>
|
*
|
||||||
* Large screen devices (such as tablets) are supported by replacing the ListView
|
* <p>Large screen devices (such as tablets) are supported by replacing the ListView with a
|
||||||
* with a GridView.
|
* GridView.
|
||||||
* <p/>
|
*
|
||||||
|
* <p>
|
||||||
*/
|
*/
|
||||||
public class SelectFolderFragment extends Fragment {
|
public class SelectFolderFragment extends Fragment {
|
||||||
|
|
||||||
|
@ -38,11 +36,10 @@ public class SelectFolderFragment extends Fragment {
|
||||||
private OnFragmentInteractionListener mListener;
|
private OnFragmentInteractionListener mListener;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
* Mandatory empty constructor for the fragment manager to instantiate the fragment (e.g. upon
|
||||||
* fragment (e.g. upon screen orientation changes).
|
* screen orientation changes).
|
||||||
*/
|
*/
|
||||||
public SelectFolderFragment() {
|
public SelectFolderFragment() {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
@ -50,17 +47,18 @@ public class SelectFolderFragment extends Fragment {
|
||||||
String path = getArguments().getString("Path");
|
String path = getArguments().getString("Path");
|
||||||
|
|
||||||
pathStack = new Stack<>();
|
pathStack = new Stack<>();
|
||||||
recyclerAdapter = new FolderRecyclerAdapter(mListener,
|
recyclerAdapter =
|
||||||
PasswordRepository.getPasswords(
|
new FolderRecyclerAdapter(
|
||||||
new File(path),
|
mListener,
|
||||||
PasswordRepository.getRepositoryDirectory(requireActivity()), getSortOrder()
|
PasswordRepository.getPasswords(
|
||||||
)
|
new File(path),
|
||||||
);
|
PasswordRepository.getRepositoryDirectory(requireActivity()),
|
||||||
|
getSortOrder()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(
|
||||||
Bundle savedInstanceState) {
|
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View view = inflater.inflate(R.layout.password_recycler_view, container, false);
|
View view = inflater.inflate(R.layout.password_recycler_view, container, false);
|
||||||
|
|
||||||
// use a linear layout manager
|
// use a linear layout manager
|
||||||
|
@ -68,7 +66,8 @@ public class SelectFolderFragment extends Fragment {
|
||||||
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||||
|
|
||||||
// use divider
|
// use divider
|
||||||
recyclerView.addItemDecoration(new DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL));
|
recyclerView.addItemDecoration(
|
||||||
|
new DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL));
|
||||||
|
|
||||||
// Set the adapter
|
// Set the adapter
|
||||||
recyclerView.setAdapter(recyclerAdapter);
|
recyclerView.setAdapter(recyclerAdapter);
|
||||||
|
@ -83,21 +82,28 @@ public class SelectFolderFragment extends Fragment {
|
||||||
public void onAttach(final Context context) {
|
public void onAttach(final Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
try {
|
try {
|
||||||
mListener = item -> {
|
mListener =
|
||||||
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
|
item -> {
|
||||||
//push the category were we're going
|
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
|
||||||
pathStack.push(item.getFile());
|
// push the category were we're going
|
||||||
|
pathStack.push(item.getFile());
|
||||||
|
|
||||||
recyclerView.scrollToPosition(0);
|
recyclerView.scrollToPosition(0);
|
||||||
recyclerAdapter.clear();
|
recyclerAdapter.clear();
|
||||||
recyclerAdapter.addAll(PasswordRepository.getPasswords(item.getFile(), PasswordRepository.getRepositoryDirectory(context), getSortOrder()));
|
recyclerAdapter.addAll(
|
||||||
|
PasswordRepository.getPasswords(
|
||||||
|
item.getFile(),
|
||||||
|
PasswordRepository.getRepositoryDirectory(context),
|
||||||
|
getSortOrder()));
|
||||||
|
|
||||||
((AppCompatActivity) requireActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
((AppCompatActivity) requireActivity())
|
||||||
}
|
.getSupportActionBar()
|
||||||
};
|
.setDisplayHomeAsUpEnabled(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
throw new ClassCastException(context.toString()
|
throw new ClassCastException(
|
||||||
+ " must implement OnFragmentInteractionListener");
|
context.toString() + " must implement OnFragmentInteractionListener");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,14 +113,13 @@ public class SelectFolderFragment extends Fragment {
|
||||||
* @return the current directory
|
* @return the current directory
|
||||||
*/
|
*/
|
||||||
public File getCurrentDir() {
|
public File getCurrentDir() {
|
||||||
if (pathStack.isEmpty())
|
if (pathStack.isEmpty()) return PasswordRepository.getRepositoryDirectory(requireContext());
|
||||||
return PasswordRepository.getRepositoryDirectory(requireContext());
|
else return pathStack.peek();
|
||||||
else
|
|
||||||
return pathStack.peek();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private PasswordRepository.PasswordSortOrder getSortOrder() {
|
private PasswordRepository.PasswordSortOrder getSortOrder() {
|
||||||
return PasswordRepository.PasswordSortOrder.getSortOrder(PreferenceManager.getDefaultSharedPreferences(requireContext()));
|
return PasswordRepository.PasswordSortOrder.getSortOrder(
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(requireContext()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface OnFragmentInteractionListener {
|
public interface OnFragmentInteractionListener {
|
||||||
|
|
|
@ -21,7 +21,6 @@ import android.widget.CheckBox;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
@ -30,18 +29,15 @@ import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
import androidx.fragment.app.FragmentActivity;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.google.android.material.textfield.TextInputEditText;
|
import com.google.android.material.textfield.TextInputEditText;
|
||||||
import com.jcraft.jsch.JSch;
|
import com.jcraft.jsch.JSch;
|
||||||
import com.jcraft.jsch.KeyPair;
|
import com.jcraft.jsch.KeyPair;
|
||||||
|
|
||||||
import org.apache.commons.io.FileUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import org.apache.commons.io.FileUtils;
|
||||||
|
|
||||||
public class SshKeyGen extends AppCompatActivity {
|
public class SshKeyGen extends AppCompatActivity {
|
||||||
|
|
||||||
|
@ -49,14 +45,15 @@ public class SshKeyGen extends AppCompatActivity {
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
if (getSupportActionBar() != null)
|
if (getSupportActionBar() != null) getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
|
||||||
|
|
||||||
setTitle("Generate SSH Key");
|
setTitle("Generate SSH Key");
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
getSupportFragmentManager().beginTransaction()
|
getSupportFragmentManager()
|
||||||
.replace(android.R.id.content, new SshKeyGenFragment()).commit();
|
.beginTransaction()
|
||||||
|
.replace(android.R.id.content, new SshKeyGenFragment())
|
||||||
|
.commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,45 +61,56 @@ public class SshKeyGen extends AppCompatActivity {
|
||||||
// private and public key, then replaces the SshKeyGenFragment with a
|
// private and public key, then replaces the SshKeyGenFragment with a
|
||||||
// ShowSshKeyFragment which displays the public key.
|
// ShowSshKeyFragment which displays the public key.
|
||||||
public void generate(View view) {
|
public void generate(View view) {
|
||||||
String length = Integer.toString((Integer) ((Spinner) findViewById(R.id.length)).getSelectedItem());
|
String length =
|
||||||
|
Integer.toString((Integer) ((Spinner) findViewById(R.id.length)).getSelectedItem());
|
||||||
String passphrase = ((EditText) findViewById(R.id.passphrase)).getText().toString();
|
String passphrase = ((EditText) findViewById(R.id.passphrase)).getText().toString();
|
||||||
String comment = ((EditText) findViewById(R.id.comment)).getText().toString();
|
String comment = ((EditText) findViewById(R.id.comment)).getText().toString();
|
||||||
new KeyGenerateTask(this).execute(length, passphrase, comment);
|
new KeyGenerateTask(this).execute(length, passphrase, comment);
|
||||||
|
|
||||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
InputMethodManager imm =
|
||||||
|
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSH key generation UI
|
// SSH key generation UI
|
||||||
public static class SshKeyGenFragment extends Fragment {
|
public static class SshKeyGenFragment extends Fragment {
|
||||||
public SshKeyGenFragment() {
|
public SshKeyGenFragment() {}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(
|
||||||
Bundle savedInstanceState) {
|
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
final View v = inflater.inflate(R.layout.fragment_ssh_keygen, container, false);
|
final View v = inflater.inflate(R.layout.fragment_ssh_keygen, container, false);
|
||||||
Typeface monoTypeface = Typeface.createFromAsset(requireContext().getAssets(), "fonts/sourcecodepro.ttf");
|
Typeface monoTypeface =
|
||||||
|
Typeface.createFromAsset(
|
||||||
|
requireContext().getAssets(), "fonts/sourcecodepro.ttf");
|
||||||
|
|
||||||
Spinner spinner = v.findViewById(R.id.length);
|
Spinner spinner = v.findViewById(R.id.length);
|
||||||
Integer[] lengths = new Integer[]{2048, 4096};
|
Integer[] lengths = new Integer[] {2048, 4096};
|
||||||
ArrayAdapter<Integer> adapter = new ArrayAdapter<>(requireContext(),
|
ArrayAdapter<Integer> adapter =
|
||||||
android.R.layout.simple_spinner_dropdown_item, lengths);
|
new ArrayAdapter<>(
|
||||||
|
requireContext(),
|
||||||
|
android.R.layout.simple_spinner_dropdown_item,
|
||||||
|
lengths);
|
||||||
spinner.setAdapter(adapter);
|
spinner.setAdapter(adapter);
|
||||||
|
|
||||||
((TextInputEditText) v.findViewById(R.id.passphrase)).setTypeface(monoTypeface);
|
((TextInputEditText) v.findViewById(R.id.passphrase)).setTypeface(monoTypeface);
|
||||||
|
|
||||||
final CheckBox checkbox = v.findViewById(R.id.show_passphrase);
|
final CheckBox checkbox = v.findViewById(R.id.show_passphrase);
|
||||||
checkbox.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
checkbox.setOnCheckedChangeListener(
|
||||||
final TextInputEditText editText = v.findViewById(R.id.passphrase);
|
(buttonView, isChecked) -> {
|
||||||
final int selection = editText.getSelectionEnd();
|
final TextInputEditText editText = v.findViewById(R.id.passphrase);
|
||||||
if (isChecked) {
|
final int selection = editText.getSelectionEnd();
|
||||||
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
|
if (isChecked) {
|
||||||
} else {
|
editText.setInputType(
|
||||||
editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
InputType.TYPE_CLASS_TEXT
|
||||||
}
|
| InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
|
||||||
editText.setSelection(selection);
|
} else {
|
||||||
});
|
editText.setInputType(
|
||||||
|
InputType.TYPE_CLASS_TEXT
|
||||||
|
| InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||||
|
}
|
||||||
|
editText.setSelection(selection);
|
||||||
|
});
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
@ -110,16 +118,17 @@ public class SshKeyGen extends AppCompatActivity {
|
||||||
|
|
||||||
// Displays the generated public key .ssh_key.pub
|
// Displays the generated public key .ssh_key.pub
|
||||||
public static class ShowSshKeyFragment extends DialogFragment {
|
public static class ShowSshKeyFragment extends DialogFragment {
|
||||||
public ShowSshKeyFragment() {
|
public ShowSshKeyFragment() {}
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
final FragmentActivity activity = requireActivity();
|
final FragmentActivity activity = requireActivity();
|
||||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireContext());
|
final MaterialAlertDialogBuilder builder =
|
||||||
|
new MaterialAlertDialogBuilder(requireContext());
|
||||||
LayoutInflater inflater = activity.getLayoutInflater();
|
LayoutInflater inflater = activity.getLayoutInflater();
|
||||||
@SuppressLint("InflateParams") final View v = inflater.inflate(R.layout.fragment_show_ssh_key, null);
|
@SuppressLint("InflateParams")
|
||||||
|
final View v = inflater.inflate(R.layout.fragment_show_ssh_key, null);
|
||||||
builder.setView(v);
|
builder.setView(v);
|
||||||
|
|
||||||
AppCompatTextView textView = v.findViewById(R.id.public_key);
|
AppCompatTextView textView = v.findViewById(R.id.public_key);
|
||||||
|
@ -131,27 +140,35 @@ public class SshKeyGen extends AppCompatActivity {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setPositiveButton(getResources().getString(R.string.dialog_ok), (dialog, which) -> {
|
builder.setPositiveButton(
|
||||||
if (activity instanceof SshKeyGen)
|
getResources().getString(R.string.dialog_ok),
|
||||||
activity.finish();
|
(dialog, which) -> {
|
||||||
});
|
if (activity instanceof SshKeyGen) activity.finish();
|
||||||
|
});
|
||||||
|
|
||||||
builder.setNegativeButton(getResources().getString(R.string.dialog_cancel), (dialog, which) -> {
|
builder.setNegativeButton(
|
||||||
|
getResources().getString(R.string.dialog_cancel), (dialog, which) -> {});
|
||||||
});
|
|
||||||
|
|
||||||
builder.setNeutralButton(getResources().getString(R.string.ssh_keygen_copy), null);
|
builder.setNeutralButton(getResources().getString(R.string.ssh_keygen_copy), null);
|
||||||
|
|
||||||
final AlertDialog ad = builder.setTitle("Your public key").create();
|
final AlertDialog ad = builder.setTitle("Your public key").create();
|
||||||
ad.setOnShowListener(dialog -> {
|
ad.setOnShowListener(
|
||||||
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
|
dialog -> {
|
||||||
b.setOnClickListener(v1 -> {
|
Button b = ad.getButton(AlertDialog.BUTTON_NEUTRAL);
|
||||||
AppCompatTextView textView1 = getDialog().findViewById(R.id.public_key);
|
b.setOnClickListener(
|
||||||
ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
|
v1 -> {
|
||||||
ClipData clip = ClipData.newPlainText("public key", textView1.getText().toString());
|
AppCompatTextView textView1 =
|
||||||
clipboard.setPrimaryClip(clip);
|
getDialog().findViewById(R.id.public_key);
|
||||||
});
|
ClipboardManager clipboard =
|
||||||
});
|
(ClipboardManager)
|
||||||
|
activity.getSystemService(
|
||||||
|
Context.CLIPBOARD_SERVICE);
|
||||||
|
ClipData clip =
|
||||||
|
ClipData.newPlainText(
|
||||||
|
"public key", textView1.getText().toString());
|
||||||
|
clipboard.setPrimaryClip(clip);
|
||||||
|
});
|
||||||
|
});
|
||||||
return ad;
|
return ad;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,17 +223,26 @@ public class SshKeyGen extends AppCompatActivity {
|
||||||
Toast.makeText(weakReference.get(), "SSH-key generated", Toast.LENGTH_LONG).show();
|
Toast.makeText(weakReference.get(), "SSH-key generated", Toast.LENGTH_LONG).show();
|
||||||
DialogFragment df = new ShowSshKeyFragment();
|
DialogFragment df = new ShowSshKeyFragment();
|
||||||
df.show(weakReference.get().getSupportFragmentManager(), "public_key");
|
df.show(weakReference.get().getSupportFragmentManager(), "public_key");
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(weakReference.get());
|
SharedPreferences prefs =
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(weakReference.get());
|
||||||
SharedPreferences.Editor editor = prefs.edit();
|
SharedPreferences.Editor editor = prefs.edit();
|
||||||
editor.putBoolean("use_generated_key", true);
|
editor.putBoolean("use_generated_key", true);
|
||||||
editor.apply();
|
editor.apply();
|
||||||
} else {
|
} else {
|
||||||
new MaterialAlertDialogBuilder(weakReference.get())
|
new MaterialAlertDialogBuilder(weakReference.get())
|
||||||
.setTitle("Error while trying to generate the ssh-key")
|
.setTitle("Error while trying to generate the ssh-key")
|
||||||
.setMessage(weakReference.get().getResources().getString(R.string.ssh_key_error_dialog_text) + e.getMessage())
|
.setMessage(
|
||||||
.setPositiveButton(weakReference.get().getResources().getString(R.string.dialog_ok), (dialogInterface, i) -> {
|
weakReference
|
||||||
// pass
|
.get()
|
||||||
}).show();
|
.getResources()
|
||||||
|
.getString(R.string.ssh_key_error_dialog_text)
|
||||||
|
+ e.getMessage())
|
||||||
|
.setPositiveButton(
|
||||||
|
weakReference.get().getResources().getString(R.string.dialog_ok),
|
||||||
|
(dialogInterface, i) -> {
|
||||||
|
// pass
|
||||||
|
})
|
||||||
|
.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,9 @@ import androidx.fragment.app.Fragment
|
||||||
class ToCloneOrNot : Fragment() {
|
class ToCloneOrNot : Fragment() {
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater,
|
||||||
savedInstanceState: Bundle?
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
): View? {
|
): View? {
|
||||||
// Inflate the layout for this fragment
|
// Inflate the layout for this fragment
|
||||||
return inflater.inflate(R.layout.fragment_to_clone_or_not, container, false)
|
return inflater.inflate(R.layout.fragment_to_clone_or_not, container, false)
|
||||||
|
|
|
@ -72,7 +72,6 @@ class UserPreference : AppCompatActivity() {
|
||||||
val externalGitRepositoryPreference = findPreference<Preference>("git_external")
|
val externalGitRepositoryPreference = findPreference<Preference>("git_external")
|
||||||
val selectExternalGitRepositoryPreference = findPreference<Preference>("pref_select_external")
|
val selectExternalGitRepositoryPreference = findPreference<Preference>("pref_select_external")
|
||||||
|
|
||||||
|
|
||||||
// Crypto preferences
|
// Crypto preferences
|
||||||
val keyPreference = findPreference<Preference>("openpgp_key_id_pref")
|
val keyPreference = findPreference<Preference>("openpgp_key_id_pref")
|
||||||
|
|
||||||
|
@ -393,8 +392,6 @@ class UserPreference : AppCompatActivity() {
|
||||||
|
|
||||||
sshKeyInputStream.close()
|
sshKeyInputStream.close()
|
||||||
sshKeyOutputSteam.close()
|
sshKeyOutputSteam.close()
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this, getString(R.string.ssh_key_does_not_exist), Toast.LENGTH_LONG).show()
|
Toast.makeText(this, getString(R.string.ssh_key_does_not_exist), Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
@ -413,8 +410,9 @@ class UserPreference : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(
|
override fun onActivityResult(
|
||||||
requestCode: Int, resultCode: Int,
|
requestCode: Int,
|
||||||
data: Intent?
|
resultCode: Int,
|
||||||
|
data: Intent?
|
||||||
) {
|
) {
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
@ -452,7 +450,6 @@ class UserPreference : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EDIT_GIT_INFO -> {
|
EDIT_GIT_INFO -> {
|
||||||
|
|
||||||
}
|
}
|
||||||
SELECT_GIT_DIRECTORY -> {
|
SELECT_GIT_DIRECTORY -> {
|
||||||
val uri = data.data
|
val uri = data.data
|
||||||
|
@ -527,7 +524,6 @@ class UserPreference : AppCompatActivity() {
|
||||||
if (passDir != null) {
|
if (passDir != null) {
|
||||||
copyDirToDir(sourcePassDir, passDir)
|
copyDirToDir(sourcePassDir, passDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -28,7 +28,6 @@ class AutofillActivity : AppCompatActivity() {
|
||||||
} catch (e: IntentSender.SendIntentException) {
|
} catch (e: IntentSender.SendIntentException) {
|
||||||
Log.e(AutofillService.Constants.TAG, "SendIntentException", e)
|
Log.e(AutofillService.Constants.TAG, "SendIntentException", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (extras != null && extras.containsKey("pick")) {
|
} else if (extras != null && extras.containsKey("pick")) {
|
||||||
val intent = Intent(applicationContext, PasswordStore::class.java)
|
val intent = Intent(applicationContext, PasswordStore::class.java)
|
||||||
intent.putExtra("matchWith", true)
|
intent.putExtra("matchWith", true)
|
||||||
|
@ -41,10 +40,10 @@ class AutofillActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
finish() // go back to the password field app
|
finish() // go back to the password field app
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
REQUEST_CODE_DECRYPT_AND_VERIFY -> if (resultCode == RESULT_OK) {
|
REQUEST_CODE_DECRYPT_AND_VERIFY -> if (resultCode == RESULT_OK) {
|
||||||
AutofillService.instance?.setResultData(data!!) // report the result to service
|
AutofillService.instance?.setResultData(data!!) // report the result to service
|
||||||
}
|
}
|
||||||
REQUEST_CODE_PICK -> if (resultCode == RESULT_OK) {
|
REQUEST_CODE_PICK -> if (resultCode == RESULT_OK) {
|
||||||
AutofillService.instance?.setPickedPassword(data!!.getStringExtra("path"))
|
AutofillService.instance?.setPickedPassword(data!!.getStringExtra("path"))
|
||||||
|
|
|
@ -26,7 +26,6 @@ import com.zeapo.pwdstore.R
|
||||||
import com.zeapo.pwdstore.utils.resolveAttribute
|
import com.zeapo.pwdstore.utils.resolveAttribute
|
||||||
import com.zeapo.pwdstore.utils.splitLines
|
import com.zeapo.pwdstore.utils.splitLines
|
||||||
|
|
||||||
|
|
||||||
class AutofillFragment : DialogFragment() {
|
class AutofillFragment : DialogFragment() {
|
||||||
private var adapter: ArrayAdapter<String>? = null
|
private var adapter: ArrayAdapter<String>? = null
|
||||||
private var isWeb: Boolean = false
|
private var isWeb: Boolean = false
|
||||||
|
@ -116,8 +115,8 @@ class AutofillFragment : DialogFragment() {
|
||||||
val editor = prefs.edit()
|
val editor = prefs.edit()
|
||||||
if (isWeb) {
|
if (isWeb) {
|
||||||
builder.setNeutralButton(R.string.autofill_apps_delete) { _, _ ->
|
builder.setNeutralButton(R.string.autofill_apps_delete) { _, _ ->
|
||||||
if (callingActivity.recyclerAdapter != null
|
if (callingActivity.recyclerAdapter != null &&
|
||||||
&& packageName != null && packageName != "") {
|
packageName != null && packageName != "") {
|
||||||
editor.remove(packageName)
|
editor.remove(packageName)
|
||||||
callingActivity.recyclerAdapter?.removeWebsite(packageName)
|
callingActivity.recyclerAdapter?.removeWebsite(packageName)
|
||||||
editor.apply()
|
editor.apply()
|
||||||
|
|
|
@ -136,7 +136,6 @@ class AutofillPreferenceActivity : AppCompatActivity() {
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
} catch (e: PackageManager.NameNotFoundException) {
|
||||||
allApps.add(AutofillRecyclerAdapter.AppInfo(key, key, true, null))
|
allApps.add(AutofillRecyclerAdapter.AppInfo(key, key, true, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
weakReference.get()?.recyclerAdapter = AutofillRecyclerAdapter(allApps, weakReference.get()!!)
|
weakReference.get()?.recyclerAdapter = AutofillRecyclerAdapter(allApps, weakReference.get()!!)
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -18,8 +18,8 @@ import java.util.ArrayList
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
internal class AutofillRecyclerAdapter(
|
internal class AutofillRecyclerAdapter(
|
||||||
allApps: List<AppInfo>,
|
allApps: List<AppInfo>,
|
||||||
private val activity: AutofillPreferenceActivity
|
private val activity: AutofillPreferenceActivity
|
||||||
) : RecyclerView.Adapter<AutofillRecyclerAdapter.ViewHolder>() {
|
) : RecyclerView.Adapter<AutofillRecyclerAdapter.ViewHolder>() {
|
||||||
|
|
||||||
private val apps: SortedList<AppInfo>
|
private val apps: SortedList<AppInfo>
|
||||||
|
@ -86,8 +86,8 @@ internal class AutofillRecyclerAdapter(
|
||||||
holder.secondary.setText(R.string.autofill_apps_match)
|
holder.secondary.setText(R.string.autofill_apps_match)
|
||||||
holder.secondary.append(" " + preference!!.splitLines()[0])
|
holder.secondary.append(" " + preference!!.splitLines()[0])
|
||||||
if (preference.trim { it <= ' ' }.splitLines().size - 1 > 0) {
|
if (preference.trim { it <= ' ' }.splitLines().size - 1 > 0) {
|
||||||
holder.secondary.append(" and "
|
holder.secondary.append(" and " +
|
||||||
+ (preference.trim { it <= ' ' }.splitLines().size - 1) + " more")
|
(preference.trim { it <= ' ' }.splitLines().size - 1) + " more")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -164,6 +164,5 @@ internal class AutofillRecyclerAdapter(
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
activity.showDialog(packageName, appName, isWeb)
|
activity.showDialog(packageName, appName, isWeb)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,17 +85,17 @@ class AutofillService : AccessibilityService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if returning to the source app from a successful AutofillActivity
|
// if returning to the source app from a successful AutofillActivity
|
||||||
if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
|
if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED &&
|
||||||
&& event.packageName != null && event.packageName == packageName
|
event.packageName != null && event.packageName == packageName &&
|
||||||
&& resultData != null) {
|
resultData != null) {
|
||||||
bindDecryptAndVerify()
|
bindDecryptAndVerify()
|
||||||
}
|
}
|
||||||
|
|
||||||
// look for webView and trigger accessibility events if window changes
|
// look for webView and trigger accessibility events if window changes
|
||||||
// or if page changes in chrome
|
// or if page changes in chrome
|
||||||
if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED || (event.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
|
if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED || (event.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED &&
|
||||||
&& event.packageName != null
|
event.packageName != null &&
|
||||||
&& (event.packageName == "com.android.chrome" || event.packageName == "com.android.browser"))) {
|
(event.packageName == "com.android.chrome" || event.packageName == "com.android.browser"))) {
|
||||||
// there is a chance for getRootInActiveWindow() to return null at any time. save it.
|
// there is a chance for getRootInActiveWindow() to return null at any time. save it.
|
||||||
try {
|
try {
|
||||||
val root = rootInActiveWindow
|
val root = rootInActiveWindow
|
||||||
|
@ -116,23 +116,20 @@ class AutofillService : AccessibilityService() {
|
||||||
webViewURL = URL("http://" + node.text.toString()).host
|
webViewURL = URL("http://" + node.text.toString()).host
|
||||||
} catch (ignored: MalformedURLException) {
|
} catch (ignored: MalformedURLException) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// sadly we were unable to access the data we wanted
|
// sadly we were unable to access the data we wanted
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// nothing to do if field is keychain app or system ui
|
// nothing to do if field is keychain app or system ui
|
||||||
if (event.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
|
if (event.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED ||
|
||||||
|| event.packageName != null && event.packageName == "org.sufficientlysecure.keychain"
|
event.packageName != null && event.packageName == "org.sufficientlysecure.keychain" ||
|
||||||
|| event.packageName != null && event.packageName == "com.android.systemui") {
|
event.packageName != null && event.packageName == "com.android.systemui") {
|
||||||
dismissDialog(event)
|
dismissDialog(event)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -281,8 +278,8 @@ class AutofillService : AccessibilityService() {
|
||||||
// means use default, so ignore it.
|
// means use default, so ignore it.
|
||||||
val value = prefs.getString(key, null)
|
val value = prefs.getString(key, null)
|
||||||
val keyLowerCase = key.toLowerCase(Locale.ROOT)
|
val keyLowerCase = key.toLowerCase(Locale.ROOT)
|
||||||
if (value != null && value != ""
|
if (value != null && value != "" &&
|
||||||
&& (webViewUrlLowerCase.contains(keyLowerCase) || keyLowerCase.contains(webViewUrlLowerCase))) {
|
(webViewUrlLowerCase.contains(keyLowerCase) || keyLowerCase.contains(webViewUrlLowerCase))) {
|
||||||
preference = value
|
preference = value
|
||||||
settingsURL = key
|
settingsURL = key
|
||||||
}
|
}
|
||||||
|
@ -397,7 +394,7 @@ class AutofillService : AccessibilityService() {
|
||||||
dialog = null
|
dialog = null
|
||||||
}
|
}
|
||||||
builder.setNeutralButton("Settings") { _, _ ->
|
builder.setNeutralButton("Settings") { _, _ ->
|
||||||
//TODO make icon? gear?
|
// TODO make icon? gear?
|
||||||
// the user will have to return to the app themselves.
|
// the user will have to return to the app themselves.
|
||||||
val intent = Intent(this@AutofillService, AutofillPreferenceActivity::class.java)
|
val intent = Intent(this@AutofillService, AutofillPreferenceActivity::class.java)
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
|
@ -426,7 +423,7 @@ class AutofillService : AccessibilityService() {
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
lastWhichItem-- // will add one element to items, so lastWhichItem=items.size()+1
|
lastWhichItem-- // will add one element to items, so lastWhichItem=items.size()+1
|
||||||
val intent = Intent(this@AutofillService, AutofillActivity::class.java)
|
val intent = Intent(this@AutofillService, AutofillActivity::class.java)
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||||
intent.putExtra("pickMatchWith", true)
|
intent.putExtra("pickMatchWith", true)
|
||||||
|
@ -512,7 +509,6 @@ class AutofillService : AccessibilityService() {
|
||||||
} catch (e: UnsupportedEncodingException) {
|
} catch (e: UnsupportedEncodingException) {
|
||||||
Log.e(Constants.TAG, "UnsupportedEncodingException", e)
|
Log.e(Constants.TAG, "UnsupportedEncodingException", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
|
OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED -> {
|
||||||
Log.i("PgpHandler", "RESULT_CODE_USER_INTERACTION_REQUIRED")
|
Log.i("PgpHandler", "RESULT_CODE_USER_INTERACTION_REQUIRED")
|
||||||
|
|
|
@ -404,7 +404,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
||||||
RESULT_CODE_USER_INTERACTION_REQUIRED -> handleUserInteractionRequest(result, REQUEST_DECRYPT)
|
RESULT_CODE_USER_INTERACTION_REQUIRED -> handleUserInteractionRequest(result, REQUEST_DECRYPT)
|
||||||
RESULT_CODE_ERROR -> handleError(result)
|
RESULT_CODE_ERROR -> handleError(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -474,7 +473,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
||||||
}
|
}
|
||||||
RESULT_CODE_ERROR -> handleError(result)
|
RESULT_CODE_ERROR -> handleError(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -669,7 +667,6 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
||||||
} else {
|
} else {
|
||||||
showToast(this.resources.getString(R.string.clipboard_password_no_clear_toast_text))
|
showToast(this.resources.getString(R.string.clipboard_password_no_clear_toast_text))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun copyUsernameToClipBoard(username: String) {
|
private fun copyUsernameToClipBoard(username: String) {
|
||||||
|
@ -697,7 +694,7 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
||||||
sendIntent,
|
sendIntent,
|
||||||
resources.getText(R.string.send_plaintext_password_to)
|
resources.getText(R.string.send_plaintext_password_to)
|
||||||
)
|
)
|
||||||
)//Always show a picker to give the user a chance to cancel
|
) // Always show a picker to give the user a chance to cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setTimer() {
|
private fun setTimer() {
|
||||||
|
@ -872,4 +869,3 @@ class PgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBound {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import java.io.File
|
||||||
/**
|
/**
|
||||||
* Creates a new clone operation
|
* Creates a new clone operation
|
||||||
*
|
*
|
||||||
* @param fileDir the git working tree directory
|
* @param fileDir the git working tree directory
|
||||||
* @param callingActivity the calling activity
|
* @param callingActivity the calling activity
|
||||||
*/
|
*/
|
||||||
class CloneOperation(fileDir: File, callingActivity: Activity) : GitOperation(fileDir, callingActivity) {
|
class CloneOperation(fileDir: File, callingActivity: Activity) : GitOperation(fileDir, callingActivity) {
|
||||||
|
@ -44,8 +44,8 @@ class CloneOperation(fileDir: File, callingActivity: Activity) : GitOperation(fi
|
||||||
/**
|
/**
|
||||||
* sets the authentication for the ssh-key scheme
|
* sets the authentication for the ssh-key scheme
|
||||||
*
|
*
|
||||||
* @param sshKey the ssh-key file
|
* @param sshKey the ssh-key file
|
||||||
* @param username the username
|
* @param username the username
|
||||||
* @param passphrase the passphrase
|
* @param passphrase the passphrase
|
||||||
* @return the current object
|
* @return the current object
|
||||||
*/
|
*/
|
||||||
|
@ -62,10 +62,10 @@ class CloneOperation(fileDir: File, callingActivity: Activity) : GitOperation(fi
|
||||||
override fun onError(errorMessage: String) {
|
override fun onError(errorMessage: String) {
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
MaterialAlertDialogBuilder(callingActivity)
|
||||||
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
||||||
.setMessage("Error occured during the clone operation, "
|
.setMessage("Error occured during the clone operation, " +
|
||||||
+ callingActivity.resources.getString(R.string.jgit_error_dialog_text)
|
callingActivity.resources.getString(R.string.jgit_error_dialog_text) +
|
||||||
+ errorMessage
|
errorMessage +
|
||||||
+ "\nPlease check the FAQ for possible reasons why this error might occur.")
|
"\nPlease check the FAQ for possible reasons why this error might occur.")
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> }
|
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,6 @@ open class GitActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNothingSelected(adapterView: AdapterView<*>) {
|
override fun onNothingSelected(adapterView: AdapterView<*>) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +106,6 @@ open class GitActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNothingSelected(adapterView: AdapterView<*>) {
|
override fun onNothingSelected(adapterView: AdapterView<*>) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,10 +216,10 @@ open class GitActivity : AppCompatActivity() {
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
when (protocol) {
|
when (protocol) {
|
||||||
"ssh://" -> {
|
"ssh://" -> {
|
||||||
var hostname = (serverUser.text.toString()
|
var hostname = (serverUser.text.toString() +
|
||||||
+ "@" +
|
"@" +
|
||||||
serverUrl.text.toString().trim { it <= ' ' }
|
serverUrl.text.toString().trim { it <= ' ' } +
|
||||||
+ ":")
|
":")
|
||||||
if (serverPort.text.toString() == "22") {
|
if (serverPort.text.toString() == "22") {
|
||||||
hostname += serverPath.text.toString()
|
hostname += serverPath.text.toString()
|
||||||
|
|
||||||
|
@ -258,7 +256,6 @@ open class GitActivity : AppCompatActivity() {
|
||||||
else -> {
|
else -> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,7 +417,6 @@ open class GitActivity : AppCompatActivity() {
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -477,8 +473,8 @@ open class GitActivity : AppCompatActivity() {
|
||||||
return
|
return
|
||||||
|
|
||||||
// Warn if non-empty folder unless it's a just-initialized store that has just a .git folder
|
// Warn if non-empty folder unless it's a just-initialized store that has just a .git folder
|
||||||
if (localDir.exists() && localDir.listFiles()!!.isNotEmpty()
|
if (localDir.exists() && localDir.listFiles()!!.isNotEmpty() &&
|
||||||
&& !(localDir.listFiles()!!.size == 1 && localDir.listFiles()!![0].name == ".git")) {
|
!(localDir.listFiles()!!.size == 1 && localDir.listFiles()!![0].name == ".git")) {
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.dialog_delete_title)
|
.setTitle(R.string.dialog_delete_title)
|
||||||
.setMessage(resources.getString(R.string.dialog_delete_msg) + " " + localDir.toString())
|
.setMessage(resources.getString(R.string.dialog_delete_msg) + " " + localDir.toString())
|
||||||
|
@ -489,7 +485,7 @@ open class GitActivity : AppCompatActivity() {
|
||||||
FileUtils.deleteDirectory(localDir)
|
FileUtils.deleteDirectory(localDir)
|
||||||
launchGitOperation(REQUEST_CLONE)
|
launchGitOperation(REQUEST_CLONE)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
//TODO Handle the exception correctly if we are unable to delete the directory...
|
// TODO Handle the exception correctly if we are unable to delete the directory...
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
||||||
}
|
}
|
||||||
|
@ -509,11 +505,10 @@ open class GitActivity : AppCompatActivity() {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
//This is what happens when jgit fails :(
|
// This is what happens when jgit fails :(
|
||||||
//TODO Handle the diffent cases of exceptions
|
// TODO Handle the diffent cases of exceptions
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
||||||
}
|
}
|
||||||
|
@ -612,11 +607,13 @@ open class GitActivity : AppCompatActivity() {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
MaterialAlertDialogBuilder(this).setMessage(e.message).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int,
|
override fun onActivityResult(
|
||||||
data: Intent?) {
|
requestCode: Int,
|
||||||
|
resultCode: Int,
|
||||||
|
data: Intent?
|
||||||
|
) {
|
||||||
|
|
||||||
// In addition to the pre-operation-launch series of intents for OpenKeychain auth
|
// In addition to the pre-operation-launch series of intents for OpenKeychain auth
|
||||||
// that will pass through here and back to launchGitOperation, there is one
|
// that will pass through here and back to launchGitOperation, there is one
|
||||||
|
|
|
@ -3,10 +3,9 @@ package com.zeapo.pwdstore.git;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.ProgressDialog;
|
import android.app.ProgressDialog;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
|
|
||||||
import com.zeapo.pwdstore.PasswordStore;
|
import com.zeapo.pwdstore.PasswordStore;
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import org.eclipse.jgit.api.CommitCommand;
|
import org.eclipse.jgit.api.CommitCommand;
|
||||||
import org.eclipse.jgit.api.GitCommand;
|
import org.eclipse.jgit.api.GitCommand;
|
||||||
import org.eclipse.jgit.api.PullCommand;
|
import org.eclipse.jgit.api.PullCommand;
|
||||||
|
@ -17,9 +16,6 @@ import org.eclipse.jgit.api.StatusCommand;
|
||||||
import org.eclipse.jgit.transport.PushResult;
|
import org.eclipse.jgit.transport.PushResult;
|
||||||
import org.eclipse.jgit.transport.RemoteRefUpdate;
|
import org.eclipse.jgit.transport.RemoteRefUpdate;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
|
|
||||||
|
|
||||||
public class GitAsyncTask extends AsyncTask<GitCommand, Integer, String> {
|
public class GitAsyncTask extends AsyncTask<GitCommand, Integer, String> {
|
||||||
private WeakReference<Activity> activityWeakReference;
|
private WeakReference<Activity> activityWeakReference;
|
||||||
private boolean finishOnEnd;
|
private boolean finishOnEnd;
|
||||||
|
@ -27,7 +23,11 @@ public class GitAsyncTask extends AsyncTask<GitCommand, Integer, String> {
|
||||||
private ProgressDialog dialog;
|
private ProgressDialog dialog;
|
||||||
private GitOperation operation;
|
private GitOperation operation;
|
||||||
|
|
||||||
public GitAsyncTask(Activity activity, boolean finishOnEnd, boolean refreshListOnEnd, GitOperation operation) {
|
public GitAsyncTask(
|
||||||
|
Activity activity,
|
||||||
|
boolean finishOnEnd,
|
||||||
|
boolean refreshListOnEnd,
|
||||||
|
GitOperation operation) {
|
||||||
this.activityWeakReference = new WeakReference<>(activity);
|
this.activityWeakReference = new WeakReference<>(activity);
|
||||||
this.finishOnEnd = finishOnEnd;
|
this.finishOnEnd = finishOnEnd;
|
||||||
this.refreshListOnEnd = refreshListOnEnd;
|
this.refreshListOnEnd = refreshListOnEnd;
|
||||||
|
@ -41,7 +41,8 @@ public class GitAsyncTask extends AsyncTask<GitCommand, Integer, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
this.dialog.setMessage(getActivity().getResources().getString(R.string.running_dialog_text));
|
this.dialog.setMessage(
|
||||||
|
getActivity().getResources().getString(R.string.running_dialog_text));
|
||||||
this.dialog.setCancelable(false);
|
this.dialog.setCancelable(false);
|
||||||
this.dialog.show();
|
this.dialog.show();
|
||||||
}
|
}
|
||||||
|
@ -58,8 +59,7 @@ public class GitAsyncTask extends AsyncTask<GitCommand, Integer, String> {
|
||||||
nbChanges = status.getChanged().size() + status.getMissing().size();
|
nbChanges = status.getChanged().size() + status.getMissing().size();
|
||||||
} else if (command instanceof CommitCommand) {
|
} else if (command instanceof CommitCommand) {
|
||||||
// the previous status will eventually be used to avoid a commit
|
// the previous status will eventually be used to avoid a commit
|
||||||
if (nbChanges == null || nbChanges > 0)
|
if (nbChanges == null || nbChanges > 0) command.call();
|
||||||
command.call();
|
|
||||||
} else if (command instanceof PullCommand) {
|
} else if (command instanceof PullCommand) {
|
||||||
final PullResult result = ((PullCommand) command).call();
|
final PullResult result = ((PullCommand) command).call();
|
||||||
final RebaseResult rr = result.getRebaseResult();
|
final RebaseResult rr = result.getRebaseResult();
|
||||||
|
@ -79,12 +79,14 @@ public class GitAsyncTask extends AsyncTask<GitCommand, Integer, String> {
|
||||||
case REJECTED_REMOTE_CHANGED:
|
case REJECTED_REMOTE_CHANGED:
|
||||||
case NON_EXISTING:
|
case NON_EXISTING:
|
||||||
case NOT_ATTEMPTED:
|
case NOT_ATTEMPTED:
|
||||||
return activity.getString(R.string.git_push_generic_error) + rru.getStatus().name();
|
return activity.getString(R.string.git_push_generic_error)
|
||||||
|
+ rru.getStatus().name();
|
||||||
case REJECTED_OTHER_REASON:
|
case REJECTED_OTHER_REASON:
|
||||||
if ("non-fast-forward".equals(rru.getMessage())) {
|
if ("non-fast-forward".equals(rru.getMessage())) {
|
||||||
return activity.getString(R.string.git_push_other_error);
|
return activity.getString(R.string.git_push_other_error);
|
||||||
} else {
|
} else {
|
||||||
return activity.getString(R.string.git_push_generic_error) + rru.getMessage();
|
return activity.getString(R.string.git_push_generic_error)
|
||||||
|
+ rru.getMessage();
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -111,8 +113,7 @@ public class GitAsyncTask extends AsyncTask<GitCommand, Integer, String> {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == null)
|
if (result == null) result = "Unexpected error";
|
||||||
result = "Unexpected error";
|
|
||||||
|
|
||||||
if (!result.isEmpty()) {
|
if (!result.isEmpty()) {
|
||||||
this.operation.onError(result);
|
this.operation.onError(result);
|
||||||
|
|
|
@ -29,7 +29,7 @@ import java.io.File
|
||||||
/**
|
/**
|
||||||
* Creates a new git operation
|
* Creates a new git operation
|
||||||
*
|
*
|
||||||
* @param fileDir the git working tree directory
|
* @param fileDir the git working tree directory
|
||||||
* @param callingActivity the calling activity
|
* @param callingActivity the calling activity
|
||||||
*/
|
*/
|
||||||
abstract class GitOperation(fileDir: File, internal val callingActivity: Activity) {
|
abstract class GitOperation(fileDir: File, internal val callingActivity: Activity) {
|
||||||
|
@ -54,8 +54,8 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
|
||||||
/**
|
/**
|
||||||
* Sets the authentication using ssh-key scheme
|
* Sets the authentication using ssh-key scheme
|
||||||
*
|
*
|
||||||
* @param sshKey the ssh-key file
|
* @param sshKey the ssh-key file
|
||||||
* @param username the username
|
* @param username the username
|
||||||
* @param passphrase the passphrase
|
* @param passphrase the passphrase
|
||||||
* @return the current object
|
* @return the current object
|
||||||
*/
|
*/
|
||||||
|
@ -87,14 +87,16 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
|
||||||
* Executes the GitCommand in an async task after creating the authentication
|
* Executes the GitCommand in an async task after creating the authentication
|
||||||
*
|
*
|
||||||
* @param connectionMode the server-connection mode
|
* @param connectionMode the server-connection mode
|
||||||
* @param username the username
|
* @param username the username
|
||||||
* @param sshKey the ssh-key file to use in ssh-key connection mode
|
* @param sshKey the ssh-key file to use in ssh-key connection mode
|
||||||
* @param identity the api identity to use for auth in OpenKeychain connection mode
|
* @param identity the api identity to use for auth in OpenKeychain connection mode
|
||||||
*/
|
*/
|
||||||
fun executeAfterAuthentication(connectionMode: String,
|
fun executeAfterAuthentication(
|
||||||
username: String,
|
connectionMode: String,
|
||||||
sshKey: File?,
|
username: String,
|
||||||
identity: SshApiSessionFactory.ApiIdentity?) {
|
sshKey: File?,
|
||||||
|
identity: SshApiSessionFactory.ApiIdentity?
|
||||||
|
) {
|
||||||
executeAfterAuthentication(connectionMode, username, sshKey, identity, false)
|
executeAfterAuthentication(connectionMode, username, sshKey, identity, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,16 +104,18 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
|
||||||
* Executes the GitCommand in an async task after creating the authentication
|
* Executes the GitCommand in an async task after creating the authentication
|
||||||
*
|
*
|
||||||
* @param connectionMode the server-connection mode
|
* @param connectionMode the server-connection mode
|
||||||
* @param username the username
|
* @param username the username
|
||||||
* @param sshKey the ssh-key file to use in ssh-key connection mode
|
* @param sshKey the ssh-key file to use in ssh-key connection mode
|
||||||
* @param identity the api identity to use for auth in OpenKeychain connection mode
|
* @param identity the api identity to use for auth in OpenKeychain connection mode
|
||||||
* @param showError show the passphrase edit text in red
|
* @param showError show the passphrase edit text in red
|
||||||
*/
|
*/
|
||||||
private fun executeAfterAuthentication(connectionMode: String,
|
private fun executeAfterAuthentication(
|
||||||
username: String,
|
connectionMode: String,
|
||||||
sshKey: File?,
|
username: String,
|
||||||
identity: SshApiSessionFactory.ApiIdentity?,
|
sshKey: File?,
|
||||||
showError: Boolean) {
|
identity: SshApiSessionFactory.ApiIdentity?,
|
||||||
|
showError: Boolean
|
||||||
|
) {
|
||||||
if (connectionMode.equals("ssh-key", ignoreCase = true)) {
|
if (connectionMode.equals("ssh-key", ignoreCase = true)) {
|
||||||
if (sshKey == null || !sshKey.exists()) {
|
if (sshKey == null || !sshKey.exists()) {
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
MaterialAlertDialogBuilder(callingActivity)
|
||||||
|
@ -199,7 +203,6 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
|
||||||
.setPositiveButton("Ok") { _, _ -> callingActivity.finish() }
|
.setPositiveButton("Ok") { _, _ -> callingActivity.finish() }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
} else if (connectionMode.equals("OpenKeychain", ignoreCase = true)) {
|
} else if (connectionMode.equals("OpenKeychain", ignoreCase = true)) {
|
||||||
setAuthentication(username, identity).execute()
|
setAuthentication(username, identity).execute()
|
||||||
|
@ -216,7 +219,6 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
|
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
|
||||||
// authenticate using the user/pwd and then execute the command
|
// authenticate using the user/pwd and then execute the command
|
||||||
setAuthentication(username, password.text.toString()).execute()
|
setAuthentication(username, password.text.toString()).execute()
|
||||||
|
|
||||||
}
|
}
|
||||||
.setNegativeButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ ->
|
.setNegativeButton(callingActivity.resources.getString(R.string.dialog_cancel)) { _, _ ->
|
||||||
callingActivity.finish()
|
callingActivity.finish()
|
||||||
|
|
|
@ -10,7 +10,7 @@ import java.io.File
|
||||||
/**
|
/**
|
||||||
* Creates a new git operation
|
* Creates a new git operation
|
||||||
*
|
*
|
||||||
* @param fileDir the git working tree directory
|
* @param fileDir the git working tree directory
|
||||||
* @param callingActivity the calling activity
|
* @param callingActivity the calling activity
|
||||||
*/
|
*/
|
||||||
class PullOperation(fileDir: File, callingActivity: Activity) : GitOperation(fileDir, callingActivity) {
|
class PullOperation(fileDir: File, callingActivity: Activity) : GitOperation(fileDir, callingActivity) {
|
||||||
|
@ -36,10 +36,10 @@ class PullOperation(fileDir: File, callingActivity: Activity) : GitOperation(fil
|
||||||
override fun onError(errorMessage: String) {
|
override fun onError(errorMessage: String) {
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
MaterialAlertDialogBuilder(callingActivity)
|
||||||
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
||||||
.setMessage("Error occured during the pull operation, "
|
.setMessage("Error occured during the pull operation, " +
|
||||||
+ callingActivity.resources.getString(R.string.jgit_error_dialog_text)
|
callingActivity.resources.getString(R.string.jgit_error_dialog_text) +
|
||||||
+ errorMessage
|
errorMessage +
|
||||||
+ "\nPlease check the FAQ for possible reasons why this error might occur.")
|
"\nPlease check the FAQ for possible reasons why this error might occur.")
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() }
|
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import java.io.File
|
||||||
/**
|
/**
|
||||||
* Creates a new git operation
|
* Creates a new git operation
|
||||||
*
|
*
|
||||||
* @param fileDir the git working tree directory
|
* @param fileDir the git working tree directory
|
||||||
* @param callingActivity the calling activity
|
* @param callingActivity the calling activity
|
||||||
*/
|
*/
|
||||||
class PushOperation(fileDir: File, callingActivity: Activity) : GitOperation(fileDir, callingActivity) {
|
class PushOperation(fileDir: File, callingActivity: Activity) : GitOperation(fileDir, callingActivity) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import java.io.File
|
||||||
/**
|
/**
|
||||||
* Creates a new git operation
|
* Creates a new git operation
|
||||||
*
|
*
|
||||||
* @param fileDir the git working tree directory
|
* @param fileDir the git working tree directory
|
||||||
* @param callingActivity the calling activity
|
* @param callingActivity the calling activity
|
||||||
*/
|
*/
|
||||||
class ResetToRemoteOperation(fileDir: File, callingActivity: Activity) : GitOperation(fileDir, callingActivity) {
|
class ResetToRemoteOperation(fileDir: File, callingActivity: Activity) : GitOperation(fileDir, callingActivity) {
|
||||||
|
@ -42,10 +42,10 @@ class ResetToRemoteOperation(fileDir: File, callingActivity: Activity) : GitOper
|
||||||
override fun onError(errorMessage: String) {
|
override fun onError(errorMessage: String) {
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
MaterialAlertDialogBuilder(callingActivity)
|
||||||
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
||||||
.setMessage("Error occured during the sync operation, "
|
.setMessage("Error occured during the sync operation, " +
|
||||||
+ "\nPlease check the FAQ for possible reasons why this error might occur."
|
"\nPlease check the FAQ for possible reasons why this error might occur." +
|
||||||
+ callingActivity.resources.getString(R.string.jgit_error_dialog_text)
|
callingActivity.resources.getString(R.string.jgit_error_dialog_text) +
|
||||||
+ errorMessage)
|
errorMessage)
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> }
|
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import java.io.File
|
||||||
/**
|
/**
|
||||||
* Creates a new git operation
|
* Creates a new git operation
|
||||||
*
|
*
|
||||||
* @param fileDir the git working tree directory
|
* @param fileDir the git working tree directory
|
||||||
* @param callingActivity the calling activity
|
* @param callingActivity the calling activity
|
||||||
*/
|
*/
|
||||||
class SyncOperation(fileDir: File, callingActivity: Activity) : GitOperation(fileDir, callingActivity) {
|
class SyncOperation(fileDir: File, callingActivity: Activity) : GitOperation(fileDir, callingActivity) {
|
||||||
|
@ -50,10 +50,10 @@ class SyncOperation(fileDir: File, callingActivity: Activity) : GitOperation(fil
|
||||||
override fun onError(errorMessage: String) {
|
override fun onError(errorMessage: String) {
|
||||||
MaterialAlertDialogBuilder(callingActivity)
|
MaterialAlertDialogBuilder(callingActivity)
|
||||||
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
.setTitle(callingActivity.resources.getString(R.string.jgit_error_dialog_title))
|
||||||
.setMessage("Error occured during the sync operation, "
|
.setMessage("Error occured during the sync operation, " +
|
||||||
+ "\nPlease check the FAQ for possible reasons why this error might occur."
|
"\nPlease check the FAQ for possible reasons why this error might occur." +
|
||||||
+ callingActivity.resources.getString(R.string.jgit_error_dialog_text)
|
callingActivity.resources.getString(R.string.jgit_error_dialog_text) +
|
||||||
+ errorMessage)
|
errorMessage)
|
||||||
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() }
|
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> callingActivity.finish() }
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,4 +19,4 @@ open class GitConfigSessionFactory : JschConfigSessionFactory() {
|
||||||
jsch.removeAllIdentity()
|
jsch.removeAllIdentity()
|
||||||
return jsch
|
return jsch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,7 @@ import android.app.Activity;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentSender;
|
import android.content.IntentSender;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.jcraft.jsch.Identity;
|
import com.jcraft.jsch.Identity;
|
||||||
import com.jcraft.jsch.JSch;
|
import com.jcraft.jsch.JSch;
|
||||||
|
@ -14,7 +12,8 @@ import com.jcraft.jsch.JSchException;
|
||||||
import com.jcraft.jsch.Session;
|
import com.jcraft.jsch.Session;
|
||||||
import com.jcraft.jsch.UserInfo;
|
import com.jcraft.jsch.UserInfo;
|
||||||
import com.zeapo.pwdstore.R;
|
import com.zeapo.pwdstore.R;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
|
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
|
||||||
import org.eclipse.jgit.transport.CredentialItem;
|
import org.eclipse.jgit.transport.CredentialItem;
|
||||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
import org.eclipse.jgit.transport.CredentialsProvider;
|
||||||
|
@ -33,15 +32,13 @@ import org.openintents.ssh.authentication.request.SigningRequest;
|
||||||
import org.openintents.ssh.authentication.request.SshPublicKeyRequest;
|
import org.openintents.ssh.authentication.request.SshPublicKeyRequest;
|
||||||
import org.openintents.ssh.authentication.util.SshAuthenticationApiUtils;
|
import org.openintents.ssh.authentication.util.SshAuthenticationApiUtils;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
|
|
||||||
public class SshApiSessionFactory extends GitConfigSessionFactory {
|
public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
/**
|
/**
|
||||||
* Intent request code indicating a completed signature that should be posted to an outstanding
|
* Intent request code indicating a completed signature that should be posted to an outstanding
|
||||||
* ApiIdentity
|
* ApiIdentity
|
||||||
*/
|
*/
|
||||||
public static final int POST_SIGNATURE = 301;
|
public static final int POST_SIGNATURE = 301;
|
||||||
|
|
||||||
private String username;
|
private String username;
|
||||||
private Identity identity;
|
private Identity identity;
|
||||||
|
|
||||||
|
@ -52,7 +49,8 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
protected JSch getJSch(@NonNull final OpenSshConfig.Host hc, @NonNull FS fs) throws JSchException {
|
protected JSch getJSch(@NonNull final OpenSshConfig.Host hc, @NonNull FS fs)
|
||||||
|
throws JSchException {
|
||||||
JSch jsch = super.getJSch(hc, fs);
|
JSch jsch = super.getJSch(hc, fs);
|
||||||
jsch.removeAllIdentity();
|
jsch.removeAllIdentity();
|
||||||
jsch.addIdentity(identity, null);
|
jsch.addIdentity(identity, null);
|
||||||
|
@ -64,36 +62,38 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
session.setConfig("StrictHostKeyChecking", "no");
|
session.setConfig("StrictHostKeyChecking", "no");
|
||||||
session.setConfig("PreferredAuthentications", "publickey");
|
session.setConfig("PreferredAuthentications", "publickey");
|
||||||
|
|
||||||
CredentialsProvider provider = new CredentialsProvider() {
|
CredentialsProvider provider =
|
||||||
@Override
|
new CredentialsProvider() {
|
||||||
public boolean isInteractive() {
|
@Override
|
||||||
return false;
|
public boolean isInteractive() {
|
||||||
}
|
return false;
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supports(CredentialItem... items) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean get(URIish uri, CredentialItem... items) throws UnsupportedCredentialItem {
|
|
||||||
for (CredentialItem item : items) {
|
|
||||||
if (item instanceof CredentialItem.Username) {
|
|
||||||
((CredentialItem.Username) item).setValue(username);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return true;
|
@Override
|
||||||
}
|
public boolean supports(CredentialItem... items) {
|
||||||
};
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean get(URIish uri, CredentialItem... items)
|
||||||
|
throws UnsupportedCredentialItem {
|
||||||
|
for (CredentialItem item : items) {
|
||||||
|
if (item instanceof CredentialItem.Username) {
|
||||||
|
((CredentialItem.Username) item).setValue(username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
UserInfo userInfo = new CredentialsProviderUserInfo(session, provider);
|
UserInfo userInfo = new CredentialsProviderUserInfo(session, provider);
|
||||||
session.setUserInfo(userInfo);
|
session.setUserInfo(userInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to build up an ApiIdentity via the invocation of several pending intents that
|
* Helper to build up an ApiIdentity via the invocation of several pending intents that
|
||||||
* communicate with OpenKeychain. The user of this class must handle onActivityResult and
|
* communicate with OpenKeychain. The user of this class must handle onActivityResult and keep
|
||||||
* keep feeding the resulting intents into the IdentityBuilder until it can successfully complete
|
* feeding the resulting intents into the IdentityBuilder until it can successfully complete the
|
||||||
* the build.
|
* build.
|
||||||
*/
|
*/
|
||||||
public static class IdentityBuilder {
|
public static class IdentityBuilder {
|
||||||
private SshAuthenticationConnection connection;
|
private SshAuthenticationConnection connection;
|
||||||
|
@ -106,12 +106,14 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
* Construct a new IdentityBuilder
|
* Construct a new IdentityBuilder
|
||||||
*
|
*
|
||||||
* @param callingActivity Activity that will be used to launch pending intents and that will
|
* @param callingActivity Activity that will be used to launch pending intents and that will
|
||||||
* receive and handle the results.
|
* receive and handle the results.
|
||||||
*/
|
*/
|
||||||
public IdentityBuilder(Activity callingActivity) {
|
public IdentityBuilder(Activity callingActivity) {
|
||||||
this.callingActivity = callingActivity;
|
this.callingActivity = callingActivity;
|
||||||
|
|
||||||
List<String> providers = SshAuthenticationApiUtils.getAuthenticationProviderPackageNames(callingActivity);
|
List<String> providers =
|
||||||
|
SshAuthenticationApiUtils.getAuthenticationProviderPackageNames(
|
||||||
|
callingActivity);
|
||||||
if (providers.isEmpty())
|
if (providers.isEmpty())
|
||||||
throw new RuntimeException(callingActivity.getString(R.string.no_ssh_api_provider));
|
throw new RuntimeException(callingActivity.getString(R.string.no_ssh_api_provider));
|
||||||
|
|
||||||
|
@ -120,42 +122,44 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
connection = new SshAuthenticationConnection(callingActivity, providers.get(0));
|
connection = new SshAuthenticationConnection(callingActivity, providers.get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Free any resources associated with this IdentityBuilder */
|
||||||
* Free any resources associated with this IdentityBuilder
|
|
||||||
*/
|
|
||||||
public void close() {
|
public void close() {
|
||||||
if (connection != null && connection.isConnected())
|
if (connection != null && connection.isConnected()) connection.disconnect();
|
||||||
connection.disconnect();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper to invoke an OpenKeyshain SSH API method and correctly interpret the result.
|
* Helper to invoke an OpenKeyshain SSH API method and correctly interpret the result.
|
||||||
*
|
*
|
||||||
* @param request The request intent to launch
|
* @param request The request intent to launch
|
||||||
* @param requestCode The request code to use if a pending intent needs to be sent
|
* @param requestCode The request code to use if a pending intent needs to be sent
|
||||||
* @return The resulting intent if the request completed immediately, or null if we had to
|
* @return The resulting intent if the request completed immediately, or null if we had to
|
||||||
* launch a pending intent to interact with the user
|
* launch a pending intent to interact with the user
|
||||||
*/
|
*/
|
||||||
private Intent executeApi(Request request, int requestCode) {
|
private Intent executeApi(Request request, int requestCode) {
|
||||||
Intent result = api.executeApi(request.toIntent());
|
Intent result = api.executeApi(request.toIntent());
|
||||||
|
|
||||||
switch (result.getIntExtra(SshAuthenticationApi.EXTRA_RESULT_CODE, -1)) {
|
switch (result.getIntExtra(SshAuthenticationApi.EXTRA_RESULT_CODE, -1)) {
|
||||||
case SshAuthenticationApi.RESULT_CODE_ERROR:
|
case SshAuthenticationApi.RESULT_CODE_ERROR:
|
||||||
SshAuthenticationApiError error = result.getParcelableExtra(SshAuthenticationApi.EXTRA_ERROR);
|
SshAuthenticationApiError error =
|
||||||
|
result.getParcelableExtra(SshAuthenticationApi.EXTRA_ERROR);
|
||||||
throw new RuntimeException(error.getMessage());
|
throw new RuntimeException(error.getMessage());
|
||||||
case SshAuthenticationApi.RESULT_CODE_SUCCESS:
|
case SshAuthenticationApi.RESULT_CODE_SUCCESS:
|
||||||
break;
|
break;
|
||||||
case SshAuthenticationApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
case SshAuthenticationApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||||
PendingIntent pendingIntent = result.getParcelableExtra(SshAuthenticationApi.EXTRA_PENDING_INTENT);
|
PendingIntent pendingIntent =
|
||||||
|
result.getParcelableExtra(SshAuthenticationApi.EXTRA_PENDING_INTENT);
|
||||||
try {
|
try {
|
||||||
callingActivity.startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0);
|
callingActivity.startIntentSenderForResult(
|
||||||
|
pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0);
|
||||||
return null;
|
return null;
|
||||||
} catch (IntentSender.SendIntentException e) {
|
} catch (IntentSender.SendIntentException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException(callingActivity.getString(R.string.ssh_api_pending_intent_failed));
|
throw new RuntimeException(
|
||||||
|
callingActivity.getString(R.string.ssh_api_pending_intent_failed));
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException(callingActivity.getString(R.string.ssh_api_unknown_error));
|
throw new RuntimeException(
|
||||||
|
callingActivity.getString(R.string.ssh_api_unknown_error));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -168,8 +172,7 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
* @param intent The intent to inspect
|
* @param intent The intent to inspect
|
||||||
*/
|
*/
|
||||||
public void consume(Intent intent) {
|
public void consume(Intent intent) {
|
||||||
if (intent == null)
|
if (intent == null) return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (intent.hasExtra(SshAuthenticationApi.EXTRA_KEY_ID)) {
|
if (intent.hasExtra(SshAuthenticationApi.EXTRA_KEY_ID)) {
|
||||||
keyId = intent.getStringExtra(SshAuthenticationApi.EXTRA_KEY_ID);
|
keyId = intent.getStringExtra(SshAuthenticationApi.EXTRA_KEY_ID);
|
||||||
|
@ -189,27 +192,31 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
*
|
*
|
||||||
* @param requestCode The request code to use if a pending intent needs to be sent
|
* @param requestCode The request code to use if a pending intent needs to be sent
|
||||||
* @return The built identity, or null of user interaction is still required (in which case
|
* @return The built identity, or null of user interaction is still required (in which case
|
||||||
* a pending intent will have already been launched)
|
* a pending intent will have already been launched)
|
||||||
*/
|
*/
|
||||||
public ApiIdentity tryBuild(int requestCode) {
|
public ApiIdentity tryBuild(int requestCode) {
|
||||||
// First gate, need to initiate a connection to the service and wait for it to connect.
|
// First gate, need to initiate a connection to the service and wait for it to connect.
|
||||||
if (api == null) {
|
if (api == null) {
|
||||||
connection.connect(new SshAuthenticationConnection.OnBound() {
|
connection.connect(
|
||||||
@Override
|
new SshAuthenticationConnection.OnBound() {
|
||||||
public void onBound(ISshAuthenticationService sshAgent) {
|
@Override
|
||||||
api = new SshAuthenticationApi(callingActivity, sshAgent);
|
public void onBound(ISshAuthenticationService sshAgent) {
|
||||||
// We can immediately try the next phase without needing to post back
|
api = new SshAuthenticationApi(callingActivity, sshAgent);
|
||||||
// though onActivityResult
|
// We can immediately try the next phase without needing to post
|
||||||
tryBuild(requestCode);
|
// back
|
||||||
}
|
// though onActivityResult
|
||||||
|
tryBuild(requestCode);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError() {
|
public void onError() {
|
||||||
new MaterialAlertDialogBuilder(callingActivity)
|
new MaterialAlertDialogBuilder(callingActivity)
|
||||||
.setMessage(callingActivity.getString(
|
.setMessage(
|
||||||
R.string.openkeychain_ssh_api_connect_fail)).show();
|
callingActivity.getString(
|
||||||
}
|
R.string.openkeychain_ssh_api_connect_fail))
|
||||||
});
|
.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -218,8 +225,7 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
if (keyId == null) {
|
if (keyId == null) {
|
||||||
consume(executeApi(new KeySelectionRequest(), requestCode));
|
consume(executeApi(new KeySelectionRequest(), requestCode));
|
||||||
// If we did not immediately get the result, bail for now and wait to be re-entered
|
// If we did not immediately get the result, bail for now and wait to be re-entered
|
||||||
if (keyId == null)
|
if (keyId == null) return null;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Third gate, need to get the public key for the selected key. This one often does not
|
// Third gate, need to get the public key for the selected key. This one often does not
|
||||||
|
@ -227,8 +233,7 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
if (publicKey == null) {
|
if (publicKey == null) {
|
||||||
consume(executeApi(new SshPublicKeyRequest(keyId), requestCode));
|
consume(executeApi(new SshPublicKeyRequest(keyId), requestCode));
|
||||||
// If we did not immediately get the result, bail for now and wait to be re-entered
|
// If we did not immediately get the result, bail for now and wait to be re-entered
|
||||||
if (publicKey == null)
|
if (publicKey == null) return null;
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Have everything we need for now, build the identify
|
// Have everything we need for now, build the identify
|
||||||
|
@ -236,9 +241,7 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** A Jsch identity that delegates key operations via the OpenKeychain SSH API */
|
||||||
* A Jsch identity that delegates key operations via the OpenKeychain SSH API
|
|
||||||
*/
|
|
||||||
public static class ApiIdentity implements Identity {
|
public static class ApiIdentity implements Identity {
|
||||||
private String keyId, description, alg;
|
private String keyId, description, alg;
|
||||||
private byte[] publicKey;
|
private byte[] publicKey;
|
||||||
|
@ -247,7 +250,13 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
private CountDownLatch latch;
|
private CountDownLatch latch;
|
||||||
private byte[] signature;
|
private byte[] signature;
|
||||||
|
|
||||||
ApiIdentity(String keyId, String description, byte[] publicKey, String alg, Activity callingActivity, SshAuthenticationApi api) {
|
ApiIdentity(
|
||||||
|
String keyId,
|
||||||
|
String description,
|
||||||
|
byte[] publicKey,
|
||||||
|
String alg,
|
||||||
|
Activity callingActivity,
|
||||||
|
SshAuthenticationApi api) {
|
||||||
this.keyId = keyId;
|
this.keyId = keyId;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
|
@ -271,33 +280,37 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
* Helper to handle the result of an OpenKeyshain SSH API signing request
|
* Helper to handle the result of an OpenKeyshain SSH API signing request
|
||||||
*
|
*
|
||||||
* @param result The result intent to handle
|
* @param result The result intent to handle
|
||||||
* @return The signed challenge, or null if it was not immediately available, in which
|
* @return The signed challenge, or null if it was not immediately available, in which case
|
||||||
* case the latch has been initialized and the pending intent started
|
* the latch has been initialized and the pending intent started
|
||||||
*/
|
*/
|
||||||
private byte[] handleSignResult(Intent result) {
|
private byte[] handleSignResult(Intent result) {
|
||||||
switch (result.getIntExtra(SshAuthenticationApi.EXTRA_RESULT_CODE, -1)) {
|
switch (result.getIntExtra(SshAuthenticationApi.EXTRA_RESULT_CODE, -1)) {
|
||||||
case SshAuthenticationApi.RESULT_CODE_ERROR:
|
case SshAuthenticationApi.RESULT_CODE_ERROR:
|
||||||
SshAuthenticationApiError error = result.getParcelableExtra(SshAuthenticationApi.EXTRA_ERROR);
|
SshAuthenticationApiError error =
|
||||||
|
result.getParcelableExtra(SshAuthenticationApi.EXTRA_ERROR);
|
||||||
throw new RuntimeException(error.getMessage());
|
throw new RuntimeException(error.getMessage());
|
||||||
case SshAuthenticationApi.RESULT_CODE_SUCCESS:
|
case SshAuthenticationApi.RESULT_CODE_SUCCESS:
|
||||||
return result.getByteArrayExtra(SshAuthenticationApi.EXTRA_SIGNATURE);
|
return result.getByteArrayExtra(SshAuthenticationApi.EXTRA_SIGNATURE);
|
||||||
case SshAuthenticationApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
case SshAuthenticationApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
|
||||||
PendingIntent pendingIntent = result.getParcelableExtra(SshAuthenticationApi.EXTRA_PENDING_INTENT);
|
PendingIntent pendingIntent =
|
||||||
|
result.getParcelableExtra(SshAuthenticationApi.EXTRA_PENDING_INTENT);
|
||||||
try {
|
try {
|
||||||
latch = new CountDownLatch(1);
|
latch = new CountDownLatch(1);
|
||||||
callingActivity.startIntentSenderForResult(pendingIntent.getIntentSender(), POST_SIGNATURE, null, 0, 0, 0);
|
callingActivity.startIntentSenderForResult(
|
||||||
|
pendingIntent.getIntentSender(), POST_SIGNATURE, null, 0, 0, 0);
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
throw new RuntimeException(callingActivity.getString(R.string.ssh_api_pending_intent_failed));
|
throw new RuntimeException(
|
||||||
|
callingActivity.getString(R.string.ssh_api_pending_intent_failed));
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if (result.hasExtra(SshAuthenticationApi.EXTRA_CHALLENGE))
|
if (result.hasExtra(SshAuthenticationApi.EXTRA_CHALLENGE))
|
||||||
return handleSignResult(api.executeApi(result));
|
return handleSignResult(api.executeApi(result));
|
||||||
throw new RuntimeException(callingActivity.getString(R.string.ssh_api_unknown_error));
|
throw new RuntimeException(
|
||||||
|
callingActivity.getString(R.string.ssh_api_unknown_error));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -318,7 +331,6 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
return signature;
|
return signature;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post a signature response back to an in-progress operation using this ApiIdentity.
|
* Post a signature response back to an in-progress operation using this ApiIdentity.
|
||||||
*
|
*
|
||||||
|
@ -328,8 +340,7 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
try {
|
try {
|
||||||
signature = handleSignResult(data);
|
signature = handleSignResult(data);
|
||||||
} finally {
|
} finally {
|
||||||
if (latch != null)
|
if (latch != null) latch.countDown();
|
||||||
latch.countDown();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,8 +365,6 @@ public class SshApiSessionFactory extends GitConfigSessionFactory {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clear() {
|
public void clear() {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import com.zeapo.pwdstore.R
|
||||||
|
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
|
||||||
|
|
||||||
object PasswordGenerator {
|
object PasswordGenerator {
|
||||||
internal const val DIGITS = 0x0001
|
internal const val DIGITS = 0x0001
|
||||||
internal const val UPPERS = 0x0002
|
internal const val UPPERS = 0x0002
|
||||||
|
@ -27,9 +26,9 @@ object PasswordGenerator {
|
||||||
/**
|
/**
|
||||||
* Sets password generation preferences.
|
* Sets password generation preferences.
|
||||||
*
|
*
|
||||||
* @param ctx context from which to retrieve SharedPreferences from
|
* @param ctx context from which to retrieve SharedPreferences from
|
||||||
* preferences file 'PasswordGenerator'
|
* preferences file 'PasswordGenerator'
|
||||||
* @param argv options for password generation
|
* @param argv options for password generation
|
||||||
* <table summary="options for password generation">
|
* <table summary="options for password generation">
|
||||||
* <tr><td>Option</td><td>Description</td></tr>
|
* <tr><td>Option</td><td>Description</td></tr>
|
||||||
* <tr><td>0</td><td>don't include numbers</td></tr>
|
* <tr><td>0</td><td>don't include numbers</td></tr>
|
||||||
|
@ -102,7 +101,7 @@ object PasswordGenerator {
|
||||||
phonemes = false
|
phonemes = false
|
||||||
pwgenFlags = pwgenFlags or NO_VOWELS // | DIGITS | UPPERS;
|
pwgenFlags = pwgenFlags or NO_VOWELS // | DIGITS | UPPERS;
|
||||||
}
|
}
|
||||||
}// pwgenFlags = DIGITS | UPPERS;
|
} // pwgenFlags = DIGITS | UPPERS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +127,6 @@ object PasswordGenerator {
|
||||||
phonemes = false
|
phonemes = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val passwords = ArrayList<String>()
|
val passwords = ArrayList<String>()
|
||||||
val num = prefs.getInt("num", 1)
|
val num = prefs.getInt("num", 1)
|
||||||
for (i in 0 until num) {
|
for (i in 0 until num) {
|
||||||
|
@ -143,4 +141,3 @@ object PasswordGenerator {
|
||||||
|
|
||||||
class PasswordGeneratorExeption(string: String) : Exception(string)
|
class PasswordGeneratorExeption(string: String) : Exception(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ internal object Phonemes {
|
||||||
/**
|
/**
|
||||||
* Generates a human-readable password.
|
* Generates a human-readable password.
|
||||||
*
|
*
|
||||||
* @param size length of password to generate
|
* @param size length of password to generate
|
||||||
* @param pwFlags flag field where set bits indicate conditions the
|
* @param pwFlags flag field where set bits indicate conditions the
|
||||||
* generated password must meet
|
* generated password must meet
|
||||||
* <table summary="bits of flag field">
|
* <table summary="bits of flag field">
|
||||||
|
@ -105,8 +105,8 @@ internal object Phonemes {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Don't allow VOWEL followed a Vowel/Dipthong pair
|
// Don't allow VOWEL followed a Vowel/Dipthong pair
|
||||||
if (prev and VOWEL > 0 && flags and VOWEL > 0
|
if (prev and VOWEL > 0 && flags and VOWEL > 0 &&
|
||||||
&& flags and DIPTHONG > 0
|
flags and DIPTHONG > 0
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -200,8 +200,8 @@ internal object Phonemes {
|
||||||
shouldBe = if (shouldBe == CONSONANT) {
|
shouldBe = if (shouldBe == CONSONANT) {
|
||||||
VOWEL
|
VOWEL
|
||||||
} else {
|
} else {
|
||||||
if (prev and VOWEL > 0 || flags and DIPTHONG > 0
|
if (prev and VOWEL > 0 || flags and DIPTHONG > 0 ||
|
||||||
|| RandomNumberGenerator.number(10) > 3
|
RandomNumberGenerator.number(10) > 3
|
||||||
) {
|
) {
|
||||||
CONSONANT
|
CONSONANT
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -5,7 +5,7 @@ internal object RandomPasswordGenerator {
|
||||||
/**
|
/**
|
||||||
* Generates a completely random password.
|
* Generates a completely random password.
|
||||||
*
|
*
|
||||||
* @param size length of password to generate
|
* @param size length of password to generate
|
||||||
* @param pwFlags flag field where set bits indicate conditions the
|
* @param pwFlags flag field where set bits indicate conditions the
|
||||||
* generated password must meet
|
* generated password must meet
|
||||||
* <table summary ="bits of flag field">
|
* <table summary ="bits of flag field">
|
||||||
|
@ -48,8 +48,8 @@ internal object RandomPasswordGenerator {
|
||||||
num = RandomNumberGenerator.number(bank.length)
|
num = RandomNumberGenerator.number(bank.length)
|
||||||
cha = bank.toCharArray()[num]
|
cha = bank.toCharArray()[num]
|
||||||
character = cha.toString()
|
character = cha.toString()
|
||||||
if (pwFlags and PasswordGenerator.AMBIGUOUS > 0
|
if (pwFlags and PasswordGenerator.AMBIGUOUS > 0 &&
|
||||||
&& PasswordGenerator.AMBIGUOUS_STR.contains(character)) {
|
PasswordGenerator.AMBIGUOUS_STR.contains(character)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (pwFlags and PasswordGenerator.NO_VOWELS > 0 && PasswordGenerator.VOWELS_STR.contains(character)) {
|
if (pwFlags and PasswordGenerator.NO_VOWELS > 0 && PasswordGenerator.VOWELS_STR.contains(character)) {
|
||||||
|
|
|
@ -98,19 +98,23 @@ abstract class EntryRecyclerAdapter internal constructor(val values: ArrayList<P
|
||||||
protected abstract fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener
|
protected abstract fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener
|
||||||
|
|
||||||
// Create new views (invoked by the layout manager)
|
// Create new views (invoked by the layout manager)
|
||||||
override fun onCreateViewHolder(parent: ViewGroup,
|
override fun onCreateViewHolder(
|
||||||
viewType: Int): ViewHolder {
|
parent: ViewGroup,
|
||||||
|
viewType: Int
|
||||||
|
): ViewHolder {
|
||||||
// create a new view
|
// create a new view
|
||||||
val v = LayoutInflater.from(parent.context)
|
val v = LayoutInflater.from(parent.context)
|
||||||
.inflate(R.layout.password_row_layout, parent, false)
|
.inflate(R.layout.password_row_layout, parent, false)
|
||||||
return ViewHolder(v)
|
return ViewHolder(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide a reference to the views for each data item
|
/*
|
||||||
// Complex data items may need more than one view per item, and
|
Provide a reference to the views for each data item
|
||||||
// you provide access to all the views for a data item in a view holder
|
Complex data items may need more than one view per item, and
|
||||||
class ViewHolder(// each data item is just a string in this case
|
you provide access to all the views for a data item in a view holder
|
||||||
val view: View) : RecyclerView.ViewHolder(view) {
|
each data item is just a string in this case
|
||||||
|
*/
|
||||||
|
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
|
||||||
val name: AppCompatTextView = view.findViewById(R.id.label)
|
val name: AppCompatTextView = view.findViewById(R.id.label)
|
||||||
val type: AppCompatTextView = view.findViewById(R.id.type)
|
val type: AppCompatTextView = view.findViewById(R.id.type)
|
||||||
val typeImage: AppCompatImageView = view.findViewById(R.id.type_image)
|
val typeImage: AppCompatImageView = view.findViewById(R.id.type_image)
|
||||||
|
|
|
@ -6,8 +6,9 @@ import com.zeapo.pwdstore.SelectFolderFragment
|
||||||
|
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
|
|
||||||
class FolderRecyclerAdapter(private val listener: SelectFolderFragment.OnFragmentInteractionListener,
|
class FolderRecyclerAdapter(
|
||||||
values: ArrayList<PasswordItem>
|
private val listener: SelectFolderFragment.OnFragmentInteractionListener,
|
||||||
|
values: ArrayList<PasswordItem>
|
||||||
) : EntryRecyclerAdapter(values) {
|
) : EntryRecyclerAdapter(values) {
|
||||||
|
|
||||||
override fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener {
|
override fun getOnClickListener(holder: ViewHolder, pass: PasswordItem): View.OnClickListener {
|
||||||
|
@ -16,5 +17,4 @@ class FolderRecyclerAdapter(private val listener: SelectFolderFragment.OnFragmen
|
||||||
notifyItemChanged(holder.adapterPosition)
|
notifyItemChanged(holder.adapterPosition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,27 @@
|
||||||
package com.zeapo.pwdstore.utils;
|
package com.zeapo.pwdstore.utils;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.apache.commons.codec.binary.Base32;
|
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import org.apache.commons.codec.binary.Base32;
|
||||||
|
|
||||||
public class Otp {
|
public class Otp {
|
||||||
|
|
||||||
private static final Base32 BASE_32 = new Base32();
|
private static final Base32 BASE_32 = new Base32();
|
||||||
|
|
||||||
private Otp() {
|
private Otp() {}
|
||||||
}
|
|
||||||
|
|
||||||
public static String calculateCode(String secret, long counter, String algorithm, String digits) {
|
public static String calculateCode(
|
||||||
String[] steam = {"2", "3", "4", "5", "6", "7", "8", "9", "B", "C",
|
String secret, long counter, String algorithm, String digits) {
|
||||||
"D", "F", "G", "H", "J", "K", "M", "N", "P", "Q",
|
String[] steam = {
|
||||||
"R", "T", "V", "W", "X", "Y"};
|
"2", "3", "4", "5", "6", "7", "8", "9", "B", "C", "D", "F", "G", "H", "J", "K", "M",
|
||||||
|
"N", "P", "Q", "R", "T", "V", "W", "X", "Y"
|
||||||
|
};
|
||||||
String ALGORITHM = "Hmac" + algorithm.toUpperCase();
|
String ALGORITHM = "Hmac" + algorithm.toUpperCase();
|
||||||
SecretKeySpec signingKey = new SecretKeySpec(BASE_32.decode(secret), ALGORITHM);
|
SecretKeySpec signingKey = new SecretKeySpec(BASE_32.decode(secret), ALGORITHM);
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@ import com.zeapo.pwdstore.crypto.PgpActivity
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
data class PasswordItem(
|
data class PasswordItem(
|
||||||
val name: String,
|
val name: String,
|
||||||
val parent: PasswordItem? = null,
|
val parent: PasswordItem? = null,
|
||||||
val type: Char,
|
val type: Char,
|
||||||
val file: File,
|
val file: File,
|
||||||
val rootDir: File
|
val rootDir: File
|
||||||
) : Comparable<PasswordItem> {
|
) : Comparable<PasswordItem> {
|
||||||
val fullPathToParent = file.absolutePath
|
val fullPathToParent = file.absolutePath
|
||||||
.replace(rootDir.absolutePath, "")
|
.replace(rootDir.absolutePath, "")
|
||||||
|
@ -41,41 +41,40 @@ data class PasswordItem(
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newCategory(
|
fun newCategory(
|
||||||
name: String,
|
name: String,
|
||||||
file: File,
|
file: File,
|
||||||
parent: PasswordItem,
|
parent: PasswordItem,
|
||||||
rootDir: File
|
rootDir: File
|
||||||
): PasswordItem {
|
): PasswordItem {
|
||||||
return PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir)
|
return PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newCategory(
|
fun newCategory(
|
||||||
name: String,
|
name: String,
|
||||||
file: File,
|
file: File,
|
||||||
rootDir: File
|
rootDir: File
|
||||||
): PasswordItem {
|
): PasswordItem {
|
||||||
return PasswordItem(name, null, TYPE_CATEGORY, file, rootDir)
|
return PasswordItem(name, null, TYPE_CATEGORY, file, rootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newPassword(
|
fun newPassword(
|
||||||
name: String,
|
name: String,
|
||||||
file: File,
|
file: File,
|
||||||
parent: PasswordItem,
|
parent: PasswordItem,
|
||||||
rootDir: File
|
rootDir: File
|
||||||
): PasswordItem {
|
): PasswordItem {
|
||||||
return PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir)
|
return PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun newPassword(
|
fun newPassword(
|
||||||
name: String,
|
name: String,
|
||||||
file: File,
|
file: File,
|
||||||
rootDir: File
|
rootDir: File
|
||||||
): PasswordItem {
|
): PasswordItem {
|
||||||
return PasswordItem(name, null, TYPE_PASSWORD, file, rootDir)
|
return PasswordItem(name, null, TYPE_PASSWORD, file, rootDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -12,9 +12,10 @@ import com.zeapo.pwdstore.R
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import java.util.TreeSet
|
import java.util.TreeSet
|
||||||
|
|
||||||
class PasswordRecyclerAdapter(private val activity: PasswordStore,
|
class PasswordRecyclerAdapter(
|
||||||
private val listener: PasswordFragment.OnFragmentInteractionListener,
|
private val activity: PasswordStore,
|
||||||
values: ArrayList<PasswordItem>
|
private val listener: PasswordFragment.OnFragmentInteractionListener,
|
||||||
|
values: ArrayList<PasswordItem>
|
||||||
) : EntryRecyclerAdapter(values) {
|
) : EntryRecyclerAdapter(values) {
|
||||||
var actionMode: ActionMode? = null
|
var actionMode: ActionMode? = null
|
||||||
private var canEdit: Boolean = false
|
private var canEdit: Boolean = false
|
||||||
|
|
|
@ -32,7 +32,6 @@ open class PasswordRepository protected constructor() {
|
||||||
(p2.type + p1.name).compareTo(p1.type + p2.name, ignoreCase = true)
|
(p2.type + p1.name).compareTo(p1.type + p2.name, ignoreCase = true)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getSortOrder(settings: SharedPreferences): PasswordSortOrder {
|
fun getSortOrder(settings: SharedPreferences): PasswordSortOrder {
|
||||||
|
@ -63,7 +62,6 @@ open class PasswordRepository protected constructor() {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return repository
|
return repository
|
||||||
}
|
}
|
||||||
|
@ -104,7 +102,6 @@ open class PasswordRepository protected constructor() {
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if (replace!!) {
|
} else if (replace!!) {
|
||||||
try {
|
try {
|
||||||
val uri = URIish(url)
|
val uri = URIish(url)
|
||||||
|
@ -127,7 +124,6 @@ open class PasswordRepository protected constructor() {
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,7 +210,7 @@ open class PasswordRepository protected constructor() {
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getPasswords(path: File, rootDir: File, sortOrder: PasswordSortOrder): ArrayList<PasswordItem> {
|
fun getPasswords(path: File, rootDir: File, sortOrder: PasswordSortOrder): ArrayList<PasswordItem> {
|
||||||
//We need to recover the passwords then parse the files
|
// We need to recover the passwords then parse the files
|
||||||
val passList = getFilesList(path)
|
val passList = getFilesList(path)
|
||||||
|
|
||||||
if (passList.size == 0) return ArrayList()
|
if (passList.size == 0) return ArrayList()
|
||||||
|
@ -259,10 +255,10 @@ open class PasswordRepository protected constructor() {
|
||||||
/**
|
/**
|
||||||
* Sets a git config value
|
* Sets a git config value
|
||||||
*
|
*
|
||||||
* @param section config section name
|
* @param section config section name
|
||||||
* @param subsection config subsection name
|
* @param subsection config subsection name
|
||||||
* @param name config name
|
* @param name config name
|
||||||
* @param value the value to be set
|
* @param value the value to be set
|
||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Suppress("SameParameterValue")
|
@Suppress("SameParameterValue")
|
||||||
|
@ -275,7 +271,6 @@ open class PasswordRepository protected constructor() {
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ import androidx.fragment.app.FragmentActivity
|
||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
|
|
||||||
internal class Authenticator(
|
internal class Authenticator(
|
||||||
private val fragmentActivity: FragmentActivity,
|
private val fragmentActivity: FragmentActivity,
|
||||||
private val callback: (AuthenticationResult) -> Unit
|
private val callback: (AuthenticationResult) -> Unit
|
||||||
) {
|
) {
|
||||||
private val handler = Handler()
|
private val handler = Handler()
|
||||||
private val biometricManager = BiometricManager.from(fragmentActivity)
|
private val biometricManager = BiometricManager.from(fragmentActivity)
|
||||||
|
@ -18,7 +18,7 @@ internal class Authenticator(
|
||||||
|
|
||||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
super.onAuthenticationError(errorCode, errString)
|
super.onAuthenticationError(errorCode, errString)
|
||||||
Log.d(TAG,"Error: $errorCode: $errString")
|
Log.d(TAG, "Error: $errorCode: $errString")
|
||||||
callback(AuthenticationResult.UnrecoverableError(errorCode, errString))
|
callback(AuthenticationResult.UnrecoverableError(errorCode, errString))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,10 @@ import android.widget.LinearLayout
|
||||||
import com.zeapo.pwdstore.R
|
import com.zeapo.pwdstore.R
|
||||||
|
|
||||||
class MultiselectableLinearLayout @JvmOverloads constructor(
|
class MultiselectableLinearLayout @JvmOverloads constructor(
|
||||||
context: Context,
|
context: Context,
|
||||||
attrs: AttributeSet? = null,
|
attrs: AttributeSet? = null,
|
||||||
defStyleAttr: Int = 0,
|
defStyleAttr: Int = 0,
|
||||||
defStyleRes: Int = 0
|
defStyleRes: Int = 0
|
||||||
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
|
) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
|
||||||
private var multiselected: Boolean = false
|
private var multiselected: Boolean = false
|
||||||
|
|
||||||
|
@ -48,4 +48,4 @@ class MultiselectableLinearLayout @JvmOverloads constructor(
|
||||||
companion object {
|
companion object {
|
||||||
private val STATE_MULTISELECTED = intArrayOf(R.attr.state_multiselected)
|
private val STATE_MULTISELECTED = intArrayOf(R.attr.state_multiselected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,3 +45,5 @@ tasks {
|
||||||
distributionType = Wrapper.DistributionType.ALL
|
distributionType = Wrapper.DistributionType.ALL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configureSpotless()
|
||||||
|
|
16
buildSrc/build.gradle.kts
Normal file
16
buildSrc/build.gradle.kts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
plugins {
|
||||||
|
`kotlin-dsl`
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
maven("https://plugins.gradle.org/m2/")
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinDslPluginOptions {
|
||||||
|
experimentalWarning.set(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation("com.diffplug.spotless:spotless-plugin-gradle:3.24.3")
|
||||||
|
}
|
47
buildSrc/src/main/kotlin/SpotlessConfiguration.kt
Normal file
47
buildSrc/src/main/kotlin/SpotlessConfiguration.kt
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import com.diffplug.gradle.spotless.SpotlessExtension
|
||||||
|
import com.diffplug.gradle.spotless.SpotlessPlugin
|
||||||
|
import org.gradle.api.Project
|
||||||
|
import org.gradle.kotlin.dsl.apply
|
||||||
|
import org.gradle.kotlin.dsl.configure
|
||||||
|
|
||||||
|
val kotlinLicenseHeader = """/*
|
||||||
|
* Copyright © 2014-2019 The Android Password Store Authors. All Rights Reserved.
|
||||||
|
* SPDX-License-Identifier: GPL-2.0
|
||||||
|
*/
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
fun Project.configureSpotless() {
|
||||||
|
apply<SpotlessPlugin>()
|
||||||
|
|
||||||
|
configure<SpotlessExtension> {
|
||||||
|
java {
|
||||||
|
target("**/src/main/**/*.java")
|
||||||
|
trimTrailingWhitespace()
|
||||||
|
// @Suppress("INACCESSIBLE_TYPE")
|
||||||
|
// licenseHeader(kotlinLicenseHeader)
|
||||||
|
removeUnusedImports()
|
||||||
|
googleJavaFormat().aosp()
|
||||||
|
endWithNewline()
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinGradle {
|
||||||
|
target("*.gradle.kts", "gradle/*.gradle.kts", "buildSrc/*.gradle.kts")
|
||||||
|
ktlint("0.31.0").userData(mapOf("indent_size" to "4", "continuation_indent_size" to "4"))
|
||||||
|
// @Suppress("INACCESSIBLE_TYPE")
|
||||||
|
// licenseHeader(kotlinLicenseHeader, "import|tasks|apply|plugins|include")
|
||||||
|
trimTrailingWhitespace()
|
||||||
|
indentWithSpaces()
|
||||||
|
endWithNewline()
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
target("**/src/main/**/*.kt", "buildSrc/**/*.kt")
|
||||||
|
ktlint("0.31.0").userData(mapOf("indent_size" to "4", "continuation_indent_size" to "4"))
|
||||||
|
// @Suppress("INACCESSIBLE_TYPE")
|
||||||
|
// licenseHeader(kotlinLicenseHeader)
|
||||||
|
trimTrailingWhitespace()
|
||||||
|
indentWithSpaces()
|
||||||
|
endWithNewline()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue