Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8e5046b
[Sync] Added CSV export and import option
OffRange Sep 15, 2023
1fd4fa1
[Sync] Added KeyGo-File export and import option
OffRange Sep 21, 2023
7210115
[Sync] Added authentication requirement
OffRange Sep 22, 2023
5d3b748
[Sync] Warning before authentication
OffRange Oct 27, 2023
f462a71
[Sync] Updated toast for import
OffRange Oct 27, 2023
da1f9d4
[Sync] Non-Cancelable Password Dialog
OffRange Oct 27, 2023
fff099f
[Sync] Added backup pref summary
OffRange Oct 27, 2023
03f4241
[Versioning] Fixed build type calculation
OffRange Sep 22, 2023
ae5d7f0
[Dependencies] Updated outdated dependencies
OffRange Oct 27, 2023
ea76d94
[Internal] Updated AGP to 8.1.2
OffRange Oct 27, 2023
7ba7d8c
[Android] Updated targetSdk and compileSdk to 34
OffRange Oct 27, 2023
5041046
[Sync] Changed backup pref summary
OffRange Oct 27, 2023
b7ddcb0
[Sync] KEYGO format option added in warning dialog
OffRange Oct 27, 2023
c862768
[Version] Updated to 1.2.0-beta03
OffRange Oct 27, 2023
a264dd3
[Sync] Fixed Element ID Export/Import Issue
OffRange Oct 28, 2023
0f5552d
[Sync] Added Loading dialog
OffRange Oct 28, 2023
8b0fe6d
[Sync] Improved CSV import duplicate detection
OffRange Oct 28, 2023
219ccdc
[Sync] Improved duplicate skipped dialog message
OffRange Oct 28, 2023
4420859
[Sync] Cancelable import password dialog
OffRange Oct 28, 2023
88b76d7
[Code] Reformatted code
OffRange Oct 28, 2023
cacb074
[Sync] Changed CSV-Export warning dialog positive button text
OffRange Oct 28, 2023
e96cc88
Merge branch 'main' into feat/import-export
OffRange Oct 28, 2023
a1a5945
[KeyGo Sync] Fixed dialog closure on empty password
OffRange Oct 29, 2023
2643123
[Backup] Enhanced implementation abstraction
OffRange Oct 29, 2023
f6cf7ca
[KeyGo Backup] Fixed import issue
OffRange Oct 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ dependencies {
githubImplementation "com.squareup.retrofit2:retrofit:2.9.0"
githubImplementation 'org.kohsuke:github-api:1.314'

implementation 'com.opencsv:opencsv:5.8'

implementation "androidx.room:room-runtime:2.6.0"
annotationProcessor "androidx.room:room-compiler:2.6.0"
implementation "androidx.room:room-rxjava3:2.6.0"
Expand Down
47 changes: 34 additions & 13 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,51 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="android.permission.NFC" />

<uses-feature android:name="android.hardware.nfc" android:required="false"/>
<uses-feature android:name="android.software.autofill" android:required="false"/>
<uses-feature
android:name="android.hardware.nfc"
android:required="false" />
<uses-feature
android:name="android.software.autofill"
android:required="false" />

<application
android:name=".PasswordManagerApplication"
android:allowBackup="false"
android:dataExtractionRules="@xml/data_extraction_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="false"
android:theme="@style/AppTheme"
android:name=".PasswordManagerApplication"
tools:targetApi="33"
android:dataExtractionRules="@xml/data_extraction_rules">
tools:targetApi="34">
<activity
android:name=".ui.sync.ImportActivity"
android:exported="true"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />

<data android:mimeType="application/octet-stream" />
<data android:pathPattern=".*\\.keygo" />
<data android:scheme="content" />
</intent-filter>
</activity>
<activity
android:name="de.davis.passwordmanager.ui.MainActivity"
android:name=".ui.MainActivity"
android:exported="false"
android:windowSoftInputMode="adjustNothing"
android:theme="@style/AppTheme.NoActionBar"/>
<activity android:name=".ui.elements.password.CreatePasswordActivity"/>
<activity android:name=".ui.elements.password.GeneratePasswordActivity" android:theme="@style/AppTheme.NoActionBar"/>
<activity android:name=".ui.elements.creditcard.CreateCreditCardActivity"/>
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustNothing" />
<activity android:name=".ui.elements.password.CreatePasswordActivity" />
<activity
android:name="de.davis.passwordmanager.ui.login.LoginActivity"
android:name=".ui.elements.password.GeneratePasswordActivity"
android:theme="@style/AppTheme.NoActionBar" />
<activity android:name=".ui.elements.creditcard.CreateCreditCardActivity" />
<activity
android:name=".ui.login.LoginActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -44,6 +64,7 @@
<intent-filter>
<action android:name="android.service.autofill.AutofillService" />
</intent-filter>

<meta-data
android:name="android.autofill"
android:resource="@xml/autofill_configuration" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class PasswordManagerApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SecureElementDatabase.createAndGet(this);

DynamicColors.applyToActivitiesIfAvailable(this);

Expand Down
137 changes: 137 additions & 0 deletions app/src/main/java/de/davis/passwordmanager/backup/DataBackup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package de.davis.passwordmanager.backup;

import static de.davis.passwordmanager.utils.BackgroundUtil.doInBackground;

import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;

import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.os.HandlerCompat;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;

import java.io.InputStream;
import java.io.OutputStream;

import javax.crypto.AEADBadTagException;

import de.davis.passwordmanager.R;
import de.davis.passwordmanager.dialog.LoadingDialog;

public abstract class DataBackup {


public static final int TYPE_EXPORT = 0;
public static final int TYPE_IMPORT = 1;

@IntDef({TYPE_EXPORT, TYPE_IMPORT})
public @interface Type{}


private final Context context;
private LoadingDialog loadingDialog;
private final Handler handler = HandlerCompat.createAsync(Looper.getMainLooper());

public DataBackup(Context context) {
this.context = context;
}

public Context getContext() {
return context;
}

@Nullable
protected abstract Result runExport(OutputStream outputStream) throws Exception;
@Nullable
protected abstract Result runImport(InputStream inputStream) throws Exception;

public void execute(@Type int type, @Nullable Uri uri){
execute(type, uri, null);
}

public void execute(@Type int type, @Nullable Uri uri, OnSyncedHandler onSyncedHandler){
ContentResolver resolver = getContext().getContentResolver();

loadingDialog = new LoadingDialog(getContext())
.setTitle(type == TYPE_EXPORT ? R.string.export : R.string.import_str)
.setMessage(R.string.wait_text);
AlertDialog alertDialog = loadingDialog.show();

doInBackground(() -> {
Result result = null;
try{
switch (type){
case TYPE_EXPORT -> result = runExport(resolver.openOutputStream(uri));
case TYPE_IMPORT -> result = runImport(resolver.openInputStream(uri));
}

handleResult(result, onSyncedHandler);
}catch (Exception e){
e.printStackTrace();
if(e instanceof NullPointerException)
return;

error(e);
}finally {
alertDialog.dismiss();
}
});
}

protected void error(Exception exception){
handler.post(() -> {
String msg = exception.getMessage();
if(exception instanceof AEADBadTagException)
msg = getContext().getString(R.string.password_does_not_match);

new MaterialAlertDialogBuilder(getContext())
.setTitle(R.string.error_title)
.setMessage(msg)
.setPositiveButton(R.string.ok, (dialog, which) -> {})
.show();
});
exception.printStackTrace();
}

protected void handleResult(Result result, OnSyncedHandler onSyncedHandler){
handler.post(() -> {
if(result instanceof Result.Error error)
new MaterialAlertDialogBuilder(getContext())
.setTitle(R.string.error_title)
.setMessage(error.getMessage())
.setPositiveButton(R.string.ok, (dialog, which) -> handleSyncHandler(onSyncedHandler, result))
.show();

else if (result instanceof Result.Duplicate duplicate)
new MaterialAlertDialogBuilder(getContext())
.setTitle(R.string.warning)
.setMessage(getContext().getResources().getQuantityString(R.plurals.item_existed, duplicate.getCount(), duplicate.getCount()))
.setPositiveButton(R.string.ok, (dialog, which) -> handleSyncHandler(onSyncedHandler, result))
.show();

else if (result instanceof Result.Success success) {
Toast.makeText(getContext(), success.getType() == TYPE_EXPORT ? R.string.backup_stored : R.string.backup_restored, Toast.LENGTH_LONG).show();
handleSyncHandler(onSyncedHandler, result);
}
});
}

private void handleSyncHandler(OnSyncedHandler onSyncedHandler, Result result){
if(onSyncedHandler != null)
onSyncedHandler.onSynced(result);
}

protected void notifyUpdate(int current, int max){
handler.post(() -> loadingDialog.updateProgress(current, max));
}

public interface OnSyncedHandler {
void onSynced(Result result);
}
}
46 changes: 46 additions & 0 deletions app/src/main/java/de/davis/passwordmanager/backup/Result.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package de.davis.passwordmanager.backup;

import static de.davis.passwordmanager.backup.DataBackup.TYPE_IMPORT;

public class Result {
public static class Success extends Result {

@DataBackup.Type
private int type;

public Success(int type) {
this.type = type;
}

public int getType() {
return type;
}
}

public static class Error extends Result {

private final String message;

public Error(String message) {
this.message = message;
}

public String getMessage() {
return message;
}
}

public static class Duplicate extends Success {

private final int count;

public Duplicate(int count) {
super(TYPE_IMPORT);
this.count = count;
}

public int getCount() {
return count;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package de.davis.passwordmanager.backup;

import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.text.InputType;
import android.widget.EditText;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;

import com.google.android.material.textfield.TextInputLayout;

import de.davis.passwordmanager.R;
import de.davis.passwordmanager.dialog.EditDialogBuilder;
import de.davis.passwordmanager.ui.views.InformationView;

public abstract class SecureDataBackup extends DataBackup {

private String password;

public SecureDataBackup(Context context) {
super(context);
}

public String getPassword() {
return password;
}

private void requestPassword(@Type int type, @Nullable Uri uri, OnSyncedHandler onSyncedHandler){
InformationView.Information i = new InformationView.Information();
i.setHint(getContext().getString(R.string.password));
i.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD);
i.setSecret(true);

AlertDialog alertDialog = new EditDialogBuilder(getContext())
.setTitle(R.string.password)
.setPositiveButton(R.string.yes, (dialog, which) -> {})
.withInformation(i)
.withStartIcon(AppCompatResources.getDrawable(getContext(), R.drawable.ic_baseline_password_24))
.setCancelable(type == TYPE_IMPORT)
.show();

/*
Needed for the error message that appears when the password (field) is empty.
otherwise the dialogue would close itself
*/
alertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
String password = ((EditText)alertDialog.findViewById(R.id.textInputEditText)).getText().toString();

if(password.isEmpty()){
((TextInputLayout)alertDialog.findViewById(R.id.textInputLayout))
.setError(getContext().getString(R.string.is_not_filled_in));
return;
}

alertDialog.dismiss();
this.password = password;


super.execute(type, uri, onSyncedHandler);
});
}

@Override
public void execute(int type, @Nullable Uri uri, OnSyncedHandler onSyncedHandler) {
requestPassword(type, uri, onSyncedHandler);
}
}
Loading