switch UserPreference to kotlin

This commit is contained in:
Mohamed Zenadi 2017-07-25 17:21:46 +01:00 committed by Mohamed Zenadi
parent 82011f7f8b
commit 2f75559ac2
2 changed files with 460 additions and 560 deletions

View file

@ -1,560 +0,0 @@
package com.zeapo.pwdstore;
import android.Manifest;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.DialogFragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
import android.widget.TextView;
import android.widget.Toast;
import com.nononsenseapps.filepicker.FilePickerActivity;
import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity;
import com.zeapo.pwdstore.crypto.PgpHandler;
import com.zeapo.pwdstore.git.GitActivity;
import com.zeapo.pwdstore.utils.PasswordRepository;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.openintents.openpgp.util.OpenPgpUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
public class UserPreference extends AppCompatActivity {
private final static int IMPORT_SSH_KEY = 1;
private final static int IMPORT_PGP_KEY = 2;
private final static int EDIT_GIT_INFO = 3;
private final static int SELECT_GIT_DIRECTORY = 4;
private final static int EXPORT_PASSWORDS = 5;
private final static int EDIT_GIT_CONFIG = 6;
private final static int REQUEST_EXTERNAL_STORAGE = 50;
private PrefsFragment prefsFragment;
public static class PrefsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final UserPreference callingActivity = (UserPreference) getActivity();
final SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences();
addPreferencesFromResource(R.xml.preference);
findPreference("app_version").setSummary(String.format("Version: %s", BuildConfig.VERSION_NAME));
findPreference("openpgp_key_id_pref").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(callingActivity, PgpHandler.class);
intent.putExtra("Operation", "GET_KEY_ID");
startActivityForResult(intent, IMPORT_PGP_KEY);
return true;
}
});
findPreference("ssh_key").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
callingActivity.getSshKeyWithPermissions(sharedPreferences.getBoolean("use_android_file_picker", false));
return true;
}
});
findPreference("ssh_keygen").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
callingActivity.makeSshKey(true);
return true;
}
});
findPreference("ssh_see_key").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
DialogFragment df = new SshKeyGen.ShowSshKeyFragment();
df.show(getFragmentManager(), "public_key");
return true;
}
});
findPreference("git_server_info").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(callingActivity, GitActivity.class);
intent.putExtra("Operation", GitActivity.EDIT_SERVER);
startActivityForResult(intent, EDIT_GIT_INFO);
return true;
}
});
findPreference("git_config").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(callingActivity, GitActivity.class);
intent.putExtra("Operation", GitActivity.EDIT_GIT_CONFIG);
startActivityForResult(intent, EDIT_GIT_CONFIG);
return true;
}
});
findPreference("git_delete_repo").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
new AlertDialog.Builder(callingActivity).
setTitle(R.string.pref_dialog_delete_title).
setMessage(getResources().getString(R.string.dialog_delete_msg)
+ " \n" + PasswordRepository.getRepositoryDirectory(callingActivity.getApplicationContext()).toString()).
setCancelable(false).
setPositiveButton(R.string.dialog_delete, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
try {
FileUtils.cleanDirectory(PasswordRepository.getRepositoryDirectory(callingActivity.getApplicationContext()));
PasswordRepository.closeRepository();
} catch (Exception e) {
//TODO Handle the diffent cases of exceptions
}
sharedPreferences.edit().putBoolean("repository_initialized", false).apply();
dialogInterface.cancel();
callingActivity.finish();
}
}).
setNegativeButton(R.string.dialog_do_not_delete,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
{
dialogInterface.cancel();
}
}
}).show();
return true;
}
});
final Preference externalRepo = findPreference("pref_select_external");
externalRepo.setSummary(getPreferenceManager().getSharedPreferences().getString("git_external_repo", callingActivity.getString(R.string.no_repo_selected)));
externalRepo.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
callingActivity.selectExternalGitRepository();
return true;
}
});
Preference.OnPreferenceChangeListener resetRepo = new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object o) {
findPreference("git_delete_repo").setEnabled(!(Boolean) o);
PasswordRepository.closeRepository();
getPreferenceManager().getSharedPreferences().edit().putBoolean("repo_changed", true).apply();
return true;
}
};
findPreference("pref_select_external").setOnPreferenceChangeListener(resetRepo);
findPreference("git_external").setOnPreferenceChangeListener(resetRepo);
findPreference("autofill_apps").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(callingActivity, AutofillPreferenceActivity.class);
startActivity(intent);
return true;
}
});
findPreference("autofill_enable").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
new AlertDialog.Builder(callingActivity).
setTitle(R.string.pref_autofill_enable_title).
setView(R.layout.autofill_instructions).
setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
startActivity(intent);
}
}).
setNegativeButton(R.string.dialog_cancel, null).
setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
((CheckBoxPreference) findPreference("autofill_enable"))
.setChecked(((UserPreference) getActivity()).isServiceEnabled());
}
}).show();
return true;
}
});
findPreference("export_passwords").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
callingActivity.exportPasswordsWithPermissions();
return true;
}
});
}
@Override
public void onStart() {
super.onStart();
final SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences();
findPreference("pref_select_external").setSummary(getPreferenceManager().getSharedPreferences().getString("git_external_repo", getString(R.string.no_repo_selected)));
findPreference("ssh_see_key").setEnabled(sharedPreferences.getBoolean("use_generated_key", false));
findPreference("git_delete_repo").setEnabled(!sharedPreferences.getBoolean("git_external", false));
Preference keyPref = findPreference("openpgp_key_id_pref");
ArrayList<String> selectedKeys = new ArrayList<>(sharedPreferences.getStringSet("openpgp_key_ids_set", new HashSet<String>()));
if (selectedKeys.isEmpty()) {
keyPref.setSummary("No key selected");
} else {
StringBuilder summaryBuilder = new StringBuilder();
for (int i = 0; i < selectedKeys.size(); ++i) {
String s = selectedKeys.get(i);
summaryBuilder.append(OpenPgpUtils.convertKeyIdToHex(Long.valueOf(s)));
if (i < selectedKeys.size() - 1)
summaryBuilder.append("; ");
}
keyPref.setSummary(summaryBuilder.toString());
}
// see if the autofill service is enabled and check the preference accordingly
((CheckBoxPreference) findPreference("autofill_enable"))
.setChecked(((UserPreference) getActivity()).isServiceEnabled());
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
super.onCreate(savedInstanceState);
if (getIntent() != null) {
if (getIntent().getStringExtra("operation") != null) {
switch (getIntent().getStringExtra("operation")) {
case "get_ssh_key":
getSshKeyWithPermissions(sharedPreferences.getBoolean("use_android_file_picker", false));
break;
case "make_ssh_key":
makeSshKey(false);
break;
case "git_external":
selectExternalGitRepository();
break;
}
}
}
prefsFragment = new PrefsFragment();
getFragmentManager().beginTransaction().replace(android.R.id.content, prefsFragment).commit();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
private void selectExternalGitRepository() {
final Activity activity = this;
new AlertDialog.Builder(this).
setTitle("Choose where to store the passwords").
setMessage("You must select a directory where to store your passwords. If you want " +
"to store your passwords within the hidden storage of the application, " +
"cancel this dialog and disable the \"External Repository\" option.").
setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// This always works
Intent i = new Intent(activity.getApplicationContext(), FilePickerActivity.class);
// This works if you defined the intent filter
// Intent i = new Intent(Intent.ACTION_GET_CONTENT);
// Set these depending on your use case. These are the defaults.
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().getPath());
startActivityForResult(i, SELECT_GIT_DIRECTORY);
}
}).
setNegativeButton(R.string.dialog_cancel, null).show();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
switch (id) {
case android.R.id.home:
setResult(RESULT_OK);
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* Opens a file explorer to import the private key
*/
private void getSshKeyWithPermissions(boolean useDefaultPicker) {
final Activity activity = this;
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.READ_EXTERNAL_STORAGE)) {
Snackbar snack = Snackbar.make(prefsFragment.getView(),
"We need access to the sd-card to import the ssh-key",
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.dialog_ok, new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_EXTERNAL_STORAGE);
}
});
snack.show();
View view = snack.getView();
TextView tv = (TextView) view.findViewById(android.support.design.R.id.snackbar_text);
tv.setTextColor(Color.WHITE);
tv.setMaxLines(10);
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, REQUEST_EXTERNAL_STORAGE);
}
} else {
getSshKey(useDefaultPicker);
}
}
/**
* Opens a file explorer to import the private key
*/
private void getSshKey(boolean useDefaultPicker) {
if (useDefaultPicker) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("*/*");
startActivityForResult(intent, IMPORT_SSH_KEY);
} else {
// This always works
Intent i = new Intent(getApplicationContext(), FilePickerActivity.class);
// This works if you defined the intent filter
// Intent i = new Intent(Intent.ACTION_GET_CONTENT);
// Set these depending on your use case. These are the defaults.
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_FILE);
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().getPath());
startActivityForResult(i, IMPORT_SSH_KEY);
}
}
private void exportPasswordsWithPermissions() {
final Activity activity = this;
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Snackbar snack = Snackbar.make(prefsFragment.getView(),
"We need access to the sd-card to export the passwords",
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.dialog_ok, new View.OnClickListener() {
@Override
public void onClick(View view) {
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_EXTERNAL_STORAGE);
}
});
snack.show();
View view = snack.getView();
TextView tv = (TextView) view.findViewById(android.support.design.R.id.snackbar_text);
tv.setTextColor(Color.WHITE);
tv.setMaxLines(10);
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_EXTERNAL_STORAGE);
}
} else {
Intent i = new Intent(getApplicationContext(), FilePickerActivity.class);
// Set these depending on your use case. These are the defaults.
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false);
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true);
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR);
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().getPath());
startActivityForResult(i, EXPORT_PASSWORDS);
}
}
/**
* Opens a key generator to generate a public/private key pair
*/
private void makeSshKey(boolean fromPreferences) {
Intent intent = new Intent(getApplicationContext(), SshKeyGen.class);
startActivity(intent);
if (!fromPreferences) {
setResult(RESULT_OK);
finish();
}
}
private void copySshKey(Uri uri) throws IOException {
InputStream sshKey = this.getContentResolver().openInputStream(uri);
byte[] privateKey = IOUtils.toByteArray(sshKey);
FileUtils.writeByteArrayToFile(new File(getFilesDir() + "/.ssh_key"), privateKey);
sshKey.close();
}
// Returns whether the autofill service is enabled
private boolean isServiceEnabled() {
AccessibilityManager am = (AccessibilityManager) this
.getSystemService(Context.ACCESSIBILITY_SERVICE);
List<AccessibilityServiceInfo> runningServices = am
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
for (AccessibilityServiceInfo service : runningServices) {
if ("com.zeapo.pwdstore/.autofill.AutofillService".equals(service.getId())) {
return true;
}
}
return false;
}
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (resultCode == RESULT_OK) {
switch (requestCode) {
case IMPORT_SSH_KEY: {
try {
final Uri uri = data.getData();
if (uri == null) {
throw new IOException("Unable to open file");
}
copySshKey(uri);
Toast.makeText(this, this.getResources().getString(R.string.ssh_key_success_dialog_title), Toast.LENGTH_LONG).show();
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("use_generated_key", false);
editor.apply();
//delete the public key from generation
File file = new File(getFilesDir() + "/.ssh_key.pub");
file.delete();
setResult(RESULT_OK);
finish();
} catch (IOException e) {
new AlertDialog.Builder(this).
setTitle(this.getResources().getString(R.string.ssh_key_error_dialog_title)).
setMessage(this.getResources().getString(R.string.ssh_key_error_dialog_text) + e.getMessage()).
setPositiveButton(this.getResources().getString(R.string.dialog_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// pass
}
}).show();
}
}
break;
case EDIT_GIT_INFO: {
}
break;
case SELECT_GIT_DIRECTORY: {
final Uri uri = data.getData();
if (uri.getPath().equals(Environment.getExternalStorageDirectory().getPath())) {
// the user wants to use the root of the sdcard as a store...
new AlertDialog.Builder(this).
setTitle("SD-Card root selected").
setMessage("You have selected the root of your sdcard for the store. " +
"This is extremely dangerous and you will lose your data " +
"as its content will, eventually, be deleted").
setPositiveButton("Remove everything", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
.edit()
.putString("git_external_repo", uri.getPath())
.apply();
}
}).
setNegativeButton(R.string.dialog_cancel, null).show();
} else {
PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
.edit()
.putString("git_external_repo", uri.getPath())
.apply();
}
}
break;
case EXPORT_PASSWORDS: {
final Uri uri = data.getData();
final File repositoryDirectory = PasswordRepository.getRepositoryDirectory(getApplicationContext());
SimpleDateFormat fmtOut = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.US);
Date date = new Date();
String password_now = "/password_store_" + fmtOut.format(date);
final File targetDirectory = new File(uri.getPath() + password_now);
if (repositoryDirectory != null) {
try {
FileUtils.copyDirectory(repositoryDirectory, targetDirectory, true);
} catch (IOException e) {
Log.d("PWD_EXPORT", "Exception happened : " + e.getMessage());
}
}
}
break;
default:
break;
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
switch (requestCode) {
case REQUEST_EXTERNAL_STORAGE: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getSshKey(sharedPreferences.getBoolean("use_android_file_picker", false));
}
}
}
}
}

View file

@ -0,0 +1,460 @@
package com.zeapo.pwdstore
import android.Manifest
import android.accessibilityservice.AccessibilityServiceInfo
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.preference.CheckBoxPreference
import android.preference.Preference
import android.preference.PreferenceFragment
import android.preference.PreferenceManager
import android.provider.Settings
import android.support.design.widget.Snackbar
import android.support.v4.app.ActivityCompat
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.support.v7.app.AppCompatActivity
import android.util.Log
import android.view.MenuItem
import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import android.widget.Toast
import com.nononsenseapps.filepicker.FilePickerActivity
import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity
import com.zeapo.pwdstore.crypto.PgpHandler
import com.zeapo.pwdstore.git.GitActivity
import com.zeapo.pwdstore.utils.PasswordRepository
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.openintents.openpgp.util.OpenPgpUtils
import java.io.File
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
class UserPreference : AppCompatActivity() {
private lateinit var prefsFragment: PrefsFragment
class PrefsFragment : PreferenceFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val callingActivity = activity as UserPreference
val sharedPreferences = preferenceManager.sharedPreferences
addPreferencesFromResource(R.xml.preference)
findPreference("app_version").summary = "Version: ${BuildConfig.VERSION_NAME}"
findPreference("openpgp_key_id_pref").onPreferenceClickListener = Preference.OnPreferenceClickListener {
val intent = Intent(callingActivity, PgpHandler::class.java)
intent.putExtra("Operation", "GET_KEY_ID")
startActivityForResult(intent, IMPORT_PGP_KEY)
true
}
findPreference("ssh_key").onPreferenceClickListener = Preference.OnPreferenceClickListener {
callingActivity.getSshKeyWithPermissions(sharedPreferences.getBoolean("use_android_file_picker", false))
true
}
findPreference("ssh_keygen").onPreferenceClickListener = Preference.OnPreferenceClickListener {
callingActivity.makeSshKey(true)
true
}
findPreference("ssh_see_key").onPreferenceClickListener = Preference.OnPreferenceClickListener {
val df = SshKeyGen.ShowSshKeyFragment()
df.show(fragmentManager, "public_key")
true
}
findPreference("git_server_info").onPreferenceClickListener = Preference.OnPreferenceClickListener {
val intent = Intent(callingActivity, GitActivity::class.java)
intent.putExtra("Operation", GitActivity.EDIT_SERVER)
startActivityForResult(intent, EDIT_GIT_INFO)
true
}
findPreference("git_config").onPreferenceClickListener = Preference.OnPreferenceClickListener {
val intent = Intent(callingActivity, GitActivity::class.java)
intent.putExtra("Operation", GitActivity.EDIT_GIT_CONFIG)
startActivityForResult(intent, EDIT_GIT_CONFIG)
true
}
findPreference("git_delete_repo").onPreferenceClickListener = Preference.OnPreferenceClickListener {
val repoDir = PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext)
AlertDialog.Builder(callingActivity)
.setTitle(R.string.pref_dialog_delete_title)
.setMessage("${resources.getString(R.string.dialog_delete_msg)} \n $repoDir")
.setCancelable(false)
.setPositiveButton(R.string.dialog_delete) { dialogInterface, _ ->
try {
FileUtils.cleanDirectory(PasswordRepository.getRepositoryDirectory(callingActivity.applicationContext))
PasswordRepository.closeRepository()
} catch (e: Exception) {
//TODO Handle the diffent cases of exceptions
}
sharedPreferences.edit().putBoolean("repository_initialized", false).apply()
dialogInterface.cancel()
callingActivity.finish()
}.setNegativeButton(R.string.dialog_do_not_delete) { dialogInterface, _ -> run { dialogInterface.cancel() } }
.show()
true
}
val externalRepo = findPreference("pref_select_external")
externalRepo.summary = sharedPreferences.getString("git_external_repo", callingActivity.getString(R.string.no_repo_selected))
externalRepo.onPreferenceClickListener = Preference.OnPreferenceClickListener {
callingActivity.selectExternalGitRepository()
true
}
val resetRepo = Preference.OnPreferenceChangeListener { _, o ->
findPreference("git_delete_repo").isEnabled = !(o as Boolean)
PasswordRepository.closeRepository()
sharedPreferences.edit().putBoolean("repo_changed", true).apply()
true
}
findPreference("pref_select_external").onPreferenceChangeListener = resetRepo
findPreference("git_external").onPreferenceChangeListener = resetRepo
findPreference("autofill_apps").onPreferenceClickListener = Preference.OnPreferenceClickListener {
val intent = Intent(callingActivity, AutofillPreferenceActivity::class.java)
startActivity(intent)
true
}
findPreference("autofill_enable").onPreferenceClickListener = Preference.OnPreferenceClickListener {
AlertDialog.Builder(callingActivity).setTitle(R.string.pref_autofill_enable_title).setView(R.layout.autofill_instructions).setPositiveButton(R.string.dialog_ok) { _, _ ->
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
startActivity(intent)
}.setNegativeButton(R.string.dialog_cancel, null).setOnDismissListener { (findPreference("autofill_enable") as CheckBoxPreference).isChecked = (activity as UserPreference).isServiceEnabled }.show()
true
}
findPreference("export_passwords").onPreferenceClickListener = Preference.OnPreferenceClickListener {
callingActivity.exportPasswordsWithPermissions()
true
}
}
override fun onStart() {
super.onStart()
val sharedPreferences = preferenceManager.sharedPreferences
findPreference("pref_select_external").summary = preferenceManager.sharedPreferences.getString("git_external_repo", getString(R.string.no_repo_selected))
findPreference("ssh_see_key").isEnabled = sharedPreferences.getBoolean("use_generated_key", false)
findPreference("git_delete_repo").isEnabled = !sharedPreferences.getBoolean("git_external", false)
val keyPref = findPreference("openpgp_key_id_pref")
val selectedKeys: Array<String> = ArrayList<String>(sharedPreferences.getStringSet("openpgp_key_ids_set", HashSet<String>())).toTypedArray()
if (selectedKeys.isEmpty()) {
keyPref.summary = "No key selected"
} else {
keyPref.summary = selectedKeys.joinToString(separator = ";") {
s ->
OpenPgpUtils.convertKeyIdToHex(java.lang.Long.valueOf(s))
}
}
// see if the autofill service is enabled and check the preference accordingly
(findPreference("autofill_enable") as CheckBoxPreference).isChecked = (activity as UserPreference).isServiceEnabled
}
}
public override fun onCreate(savedInstanceState: Bundle?) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this.applicationContext)
super.onCreate(savedInstanceState)
when (intent?.getStringExtra("operation")) {
"get_ssh_key" -> getSshKeyWithPermissions(sharedPreferences.getBoolean("use_android_file_picker", false))
"make_ssh_key" -> makeSshKey(false)
"git_external" -> selectExternalGitRepository()
}
prefsFragment = PrefsFragment()
fragmentManager.beginTransaction().replace(android.R.id.content, prefsFragment).commit()
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
fun selectExternalGitRepository() {
val activity = this
AlertDialog.Builder(this)
.setTitle("Choose where to store the passwords")
.setMessage("You must select a directory where to store your passwords. If you want " +
"to store your passwords within the hidden storage of the application, " +
"cancel this dialog and disable the \"External Repository\" option.")
.setPositiveButton(R.string.dialog_ok) { _, _ ->
// This always works
val i = Intent(activity.applicationContext, FilePickerActivity::class.java)
// This works if you defined the intent filter
// Intent i = new Intent(Intent.ACTION_GET_CONTENT);
// Set these depending on your use case. These are the defaults.
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().path)
startActivityForResult(i, SELECT_GIT_DIRECTORY)
}.setNegativeButton(R.string.dialog_cancel, null).show()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
val id = item.itemId
when (id) {
android.R.id.home -> {
setResult(Activity.RESULT_OK)
finish()
return true
}
}
return super.onOptionsItemSelected(item)
}
/**
* Opens a file explorer to import the private key
*/
fun getSshKeyWithPermissions(useDefaultPicker: Boolean) = runWithPermissions(
requestedPermission = Manifest.permission.READ_EXTERNAL_STORAGE,
requestCode = REQUEST_EXTERNAL_STORAGE_SSH_KEY,
reason = "We need access to the sd-card to import the ssh-key"
) {
getSshKey(useDefaultPicker)
}
/**
* Opens a file explorer to import the private key
*/
fun getSshKey(useDefaultPicker: Boolean) {
if (useDefaultPicker) {
val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.type = "*/*"
startActivityForResult(intent, IMPORT_SSH_KEY)
} else {
// This always works
val i = Intent(applicationContext, FilePickerActivity::class.java)
// This works if you defined the intent filter
// Intent i = new Intent(Intent.ACTION_GET_CONTENT);
// Set these depending on your use case. These are the defaults.
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, false)
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_FILE)
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().path)
}
}
/**
* Run a function after checking that the permissions have been requested
*
* @param requestedPermission The permission to request
* @param requestCode The code passed to onRequestPermissionsResult
* @param reason The text to be shown to the user to explain why we're requesting this permission
* @param body The function to run
*/
fun runWithPermissions(requestedPermission: String, requestCode: Int, reason: String, body: () -> Unit): Unit {
if (ContextCompat.checkSelfPermission(this, requestedPermission) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, requestedPermission)) {
val snack = Snackbar.make(prefsFragment.view,
reason,
Snackbar.LENGTH_INDEFINITE)
.setAction(R.string.dialog_ok) {
ActivityCompat.requestPermissions(this, arrayOf(requestedPermission), requestCode)
}
snack.show()
val view = snack.view
val tv = view.findViewById(android.support.design.R.id.snackbar_text) as TextView
tv.setTextColor(Color.WHITE)
tv.maxLines = 10
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(this, arrayOf(requestedPermission), requestCode)
}
} else {
body()
}
}
/**
* Exports the passwords after requesting permissions
*/
fun exportPasswordsWithPermissions() = runWithPermissions(
requestedPermission = Manifest.permission.WRITE_EXTERNAL_STORAGE,
requestCode = REQUEST_EXTERNAL_STORAGE_SSH_KEY,
reason = "We need access to the sd-card to export the passwords"
) {
exportPasswords()
}
/**
* Exports the passwords
*/
fun exportPasswords(): Unit {
val i = Intent(applicationContext, FilePickerActivity::class.java)
// Set these depending on your use case. These are the defaults.
i.putExtra(FilePickerActivity.EXTRA_ALLOW_MULTIPLE, false)
i.putExtra(FilePickerActivity.EXTRA_ALLOW_CREATE_DIR, true)
i.putExtra(FilePickerActivity.EXTRA_MODE, FilePickerActivity.MODE_DIR)
i.putExtra(FilePickerActivity.EXTRA_START_PATH, Environment.getExternalStorageDirectory().path)
startActivityForResult(i, EXPORT_PASSWORDS)
}
/**
* Opens a key generator to generate a public/private key pair
*/
fun makeSshKey(fromPreferences: Boolean) {
val intent = Intent(applicationContext, SshKeyGen::class.java)
startActivity(intent)
if (!fromPreferences) {
setResult(Activity.RESULT_OK)
finish()
}
}
@Throws(IOException::class)
private fun copySshKey(uri: Uri) {
val sshKey = this.contentResolver.openInputStream(uri)
val privateKey = IOUtils.toByteArray(sshKey!!)
FileUtils.writeByteArrayToFile(File(filesDir.toString() + "/.ssh_key"), privateKey)
sshKey.close()
}
// Returns whether the autofill service is enabled
private val isServiceEnabled: Boolean
get() {
val am = this
.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
val runningServices = am
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC)
return runningServices.any { "com.zeapo.pwdstore/.autofill.AutofillService" == it.id }
}
override fun onActivityResult(requestCode: Int, resultCode: Int,
data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
if (data == null) {
setResult(Activity.RESULT_CANCELED)
return
}
when (requestCode) {
IMPORT_SSH_KEY -> {
try {
val uri: Uri = data.data ?: throw IOException("Unable to open file")
copySshKey(uri)
Toast.makeText(this, this.resources.getString(R.string.ssh_key_success_dialog_title), Toast.LENGTH_LONG).show()
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
prefs.edit().putBoolean("use_generated_key", false).apply()
//delete the public key from generation
val file = File(filesDir.toString() + "/.ssh_key.pub")
file.delete()
setResult(Activity.RESULT_OK)
finish()
} catch (e: IOException) {
AlertDialog.Builder(this).setTitle(this.resources.getString(R.string.ssh_key_error_dialog_title)).setMessage(this.resources.getString(R.string.ssh_key_error_dialog_text) + e.message).setPositiveButton(this.resources.getString(R.string.dialog_ok)) { _, _ ->
// pass
}.show()
}
}
EDIT_GIT_INFO -> {
}
SELECT_GIT_DIRECTORY -> {
val uri = data.data
if (uri.path == Environment.getExternalStorageDirectory().path) {
// the user wants to use the root of the sdcard as a store...
AlertDialog.Builder(this)
.setTitle("SD-Card root selected")
.setMessage("You have selected the root of your sdcard for the store. " +
"This is extremely dangerous and you will lose your data " +
"as its content will, eventually, be deleted")
.setPositiveButton("Remove everything") { _, _ ->
PreferenceManager.getDefaultSharedPreferences(applicationContext)
.edit()
.putString("git_external_repo", uri.path)
.apply()
}.setNegativeButton(R.string.dialog_cancel, null).show()
} else {
PreferenceManager.getDefaultSharedPreferences(applicationContext)
.edit()
.putString("git_external_repo", uri.path)
.apply()
}
}
EXPORT_PASSWORDS -> {
val uri = data.data
val repositoryDirectory = PasswordRepository.getRepositoryDirectory(applicationContext)
val fmtOut = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.US)
val date = Date()
val password_now = "/password_store_" + fmtOut.format(date)
val targetDirectory = File(uri.path + password_now)
if (repositoryDirectory != null) {
try {
FileUtils.copyDirectory(repositoryDirectory, targetDirectory, true)
} catch (e: IOException) {
Log.d("PWD_EXPORT", "Exception happened : " + e.message)
}
}
}
else -> {
}
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this.applicationContext)
when (requestCode) {
REQUEST_EXTERNAL_STORAGE_SSH_KEY -> {
// If request is cancelled, the result arrays are empty.
if (grantResults.isNotEmpty() && PackageManager.PERMISSION_GRANTED in grantResults) {
getSshKey(sharedPreferences.getBoolean("use_android_file_picker", false))
}
}
REQUEST_EXTERNAL_STORAGE_EXPORT_PWD -> {
if (grantResults.isNotEmpty() && PackageManager.PERMISSION_GRANTED in grantResults) {
exportPasswords()
}
}
}
}
companion object {
private val IMPORT_SSH_KEY = 1
private val IMPORT_PGP_KEY = 2
private val EDIT_GIT_INFO = 3
private val SELECT_GIT_DIRECTORY = 4
private val EXPORT_PASSWORDS = 5
private val EDIT_GIT_CONFIG = 6
private val REQUEST_EXTERNAL_STORAGE_SSH_KEY = 50
private val REQUEST_EXTERNAL_STORAGE_EXPORT_PWD = 51
}
}