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:
Harsh Shandilya 2019-10-02 18:04:18 +05:30 committed by GitHub
parent 9a1a54a6fc
commit f1f59dc1ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 907 additions and 682 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -19,4 +19,4 @@ open class GitConfigSessionFactory : JschConfigSessionFactory() {
jsch.removeAllIdentity() jsch.removeAllIdentity()
return jsch return jsch
} }
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -45,3 +45,5 @@ tasks {
distributionType = Wrapper.DistributionType.ALL distributionType = Wrapper.DistributionType.ALL
} }
} }
configureSpotless()

16
buildSrc/build.gradle.kts Normal file
View 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")
}

View 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()
}
}
}