diff --git a/app/src/main/java/com/kunzisoft/keepass/database/Database.java b/app/src/main/java/com/kunzisoft/keepass/database/Database.java index 433a973c4..bdb2f83e7 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/Database.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/Database.java @@ -27,6 +27,7 @@ import android.preference.PreferenceManager; import android.util.Log; import com.kunzisoft.keepass.R; +import com.kunzisoft.keepass.crypto.engine.CipherEngine; import com.kunzisoft.keepass.database.exception.ContentFileNotFoundException; import com.kunzisoft.keepass.database.exception.InvalidDBException; import com.kunzisoft.keepass.database.exception.InvalidPasswordException; @@ -348,6 +349,20 @@ public class Database { } } + public PwEncryptionAlgorithm getEncryptionAlgorithm() { + return getPwDatabase().getEncryptionAlgorithm(); + } + + public void assignEncryptionAlgorithm(PwEncryptionAlgorithm algorithm) { + switch (getPwDatabase().getVersion()) { + case V4: + ((PwDatabaseV4) getPwDatabase()).setEncryptionAlgorithm(algorithm); + // TODO Simplify + ((PwDatabaseV4) getPwDatabase()).setDataEngine(algorithm.getCipherEngine()); + ((PwDatabaseV4) getPwDatabase()).setDataCipher(algorithm.getDataCipher()); + } + } + public String getEncryptionAlgorithmName(Resources resources) { return getPwDatabase().getEncryptionAlgorithm().getName(resources); } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/PwDatabaseV4.java b/app/src/main/java/com/kunzisoft/keepass/database/PwDatabaseV4.java index 0175e2a0e..37cf7946b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/PwDatabaseV4.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/PwDatabaseV4.java @@ -131,7 +131,6 @@ public class PwDatabaseV4 extends PwDatabase { public void setDataEngine(CipherEngine dataEngine) { this.dataEngine = dataEngine; - this.algorithm = dataEngine.getPwEncryptionAlgorithm(); } public PwCompressionAlgorithm getCompressionAlgorithm() { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/PwEncryptionAlgorithm.java b/app/src/main/java/com/kunzisoft/keepass/database/PwEncryptionAlgorithm.java index d75b49c29..b27085564 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/PwEncryptionAlgorithm.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/PwEncryptionAlgorithm.java @@ -22,6 +22,12 @@ package com.kunzisoft.keepass.database; import android.content.res.Resources; import com.kunzisoft.keepass.R; +import com.kunzisoft.keepass.crypto.engine.AesEngine; +import com.kunzisoft.keepass.crypto.engine.ChaCha20Engine; +import com.kunzisoft.keepass.crypto.engine.CipherEngine; +import com.kunzisoft.keepass.crypto.engine.TwofishEngine; + +import java.util.UUID; public enum PwEncryptionAlgorithm { @@ -40,4 +46,28 @@ public enum PwEncryptionAlgorithm { return resources.getString(R.string.chacha20); } } + + public CipherEngine getCipherEngine() { + switch (this) { + default: + case AES_Rijndael: + return new AesEngine(); + case Twofish: + return new TwofishEngine(); + case ChaCha20: + return new ChaCha20Engine(); + } + } + + public UUID getDataCipher() { + switch (this) { + default: + case AES_Rijndael: + return AesEngine.CIPHER_UUID; + case Twofish: + return TwofishEngine.CIPHER_UUID; + case ChaCha20: + return ChaCha20Engine.CIPHER_UUID; + } + } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.java b/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.java index 5d98c2a46..9a412befc 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/load/ImporterV4.java @@ -123,6 +123,7 @@ public class ImporterV4 extends Importer { try { engine = CipherFactory.getInstance(db.getDataCipher()); db.setDataEngine(engine); + db.setEncryptionAlgorithm(engine.getPwEncryptionAlgorithm()); cipher = engine.getCipher(Cipher.DECRYPT_MODE, db.getFinalKey(), header.encryptionIV); } catch (NoSuchAlgorithmException e) { throw new IOException("Invalid algorithm."); diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.java b/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.java index 0bda2ae58..d6c724957 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.java +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.java @@ -402,7 +402,7 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat assert fragmentManager != null; try { // don't check if we can ((SwitchPreference) preference).setChecked(false); - } catch (Exception e) {} + } catch (Exception ignored) {} new UnderDevelopmentFeatureDialogFragment().show(getFragmentManager(), "underDevFeatureDialog"); return false; }); diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preference/AlgorithmListPreference.java b/app/src/main/java/com/kunzisoft/keepass/settings/preference/AlgorithmListPreference.java new file mode 100644 index 000000000..509d7d271 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preference/AlgorithmListPreference.java @@ -0,0 +1,31 @@ +package com.kunzisoft.keepass.settings.preference; + +import android.content.Context; +import android.support.v7.preference.DialogPreference; +import android.util.AttributeSet; + +import com.kunzisoft.keepass.R; + +public class AlgorithmListPreference extends DialogPreference { + + public AlgorithmListPreference(Context context) { + this(context, null); + } + + public AlgorithmListPreference(Context context, AttributeSet attrs) { + this(context, attrs, R.attr.dialogPreferenceStyle); + } + + public AlgorithmListPreference(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, defStyleAttr); + } + + public AlgorithmListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public int getDialogLayoutResource() { + return R.layout.pref_dialog_list_explanation; + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/DatabaseAlgorithmPreferenceDialogFragmentCompat.java b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/DatabaseAlgorithmPreferenceDialogFragmentCompat.java new file mode 100644 index 000000000..d769932a6 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/DatabaseAlgorithmPreferenceDialogFragmentCompat.java @@ -0,0 +1,195 @@ +package com.kunzisoft.keepass.settings.preferenceDialogFragment; + +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.NonNull; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RadioButton; +import android.widget.TextView; + +import com.kunzisoft.keepass.R; +import com.kunzisoft.keepass.database.PwEncryptionAlgorithm; +import com.kunzisoft.keepass.database.edit.OnFinish; + +import java.util.ArrayList; +import java.util.List; + +public class DatabaseAlgorithmPreferenceDialogFragmentCompat extends DatabaseSavePreferenceDialogFragmentCompat { + + private RecyclerView recyclerView; + private AlgorithmAdapter algorithmAdapter; + private OnAlgorithmClickCallback onAlgorithmClickCallback; + private PwEncryptionAlgorithm algorithmSelected; + + public static DatabaseAlgorithmPreferenceDialogFragmentCompat newInstance( + String key) { + final DatabaseAlgorithmPreferenceDialogFragmentCompat + fragment = new DatabaseAlgorithmPreferenceDialogFragmentCompat(); + final Bundle b = new Bundle(1); + b.putString(ARG_KEY, key); + fragment.setArguments(b); + + return fragment; + } + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + recyclerView = view.findViewById(R.id.pref_dialog_list); + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + algorithmAdapter = new AlgorithmAdapter(); + recyclerView.setAdapter(algorithmAdapter); + } + + public void setAlgorithm(List algorithmList, PwEncryptionAlgorithm algorithm) { + if (algorithmAdapter != null) + algorithmAdapter.setAlgorithms(algorithmList, algorithm); + algorithmSelected = algorithm; + } + + private class AlgorithmAdapter extends RecyclerView.Adapter { + + private LayoutInflater inflater; + + private List algorithms; + private PwEncryptionAlgorithm algorithmUsed; + private OnAlgorithmClickListener onAlgorithmClickListener; + + public AlgorithmAdapter() { + this.inflater = LayoutInflater.from(getContext()); + this.algorithms = new ArrayList<>(); + this.algorithmUsed = null; + this.onAlgorithmClickListener = new OnAlgorithmClickListener(); + } + + @NonNull + @Override + public AlgorithmViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = inflater.inflate(R.layout.list_nodes_group, parent, false); + return new AlgorithmViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull AlgorithmViewHolder holder, int position) { + PwEncryptionAlgorithm algorithm = this.algorithms.get(position); + holder.nameView.setText(algorithm.getName(getResources())); + if (algorithmUsed != null && algorithmUsed.equals(algorithm)) + holder.radioButton.setChecked(true); + else + holder.radioButton.setChecked(false); + onAlgorithmClickListener.setAlgorithm(algorithm); + holder.container.setOnClickListener(onAlgorithmClickListener); + } + + @Override + public int getItemCount() { + return algorithms.size(); + } + + public void setAlgorithms(List algorithms, PwEncryptionAlgorithm algorithmUsed) { + this.algorithms.clear(); + this.algorithms.addAll(algorithms); + this.algorithmUsed = algorithmUsed; + } + } + + private class AlgorithmViewHolder extends RecyclerView.ViewHolder { + + View container; + RadioButton radioButton; + TextView nameView; + + public AlgorithmViewHolder(View itemView) { + super(itemView); + + container = itemView.findViewById(R.id.pref_dialog_list_container); + radioButton = itemView.findViewById(R.id.pref_dialog_list_radio); + nameView = itemView.findViewById(R.id.pref_dialog_list_name); + } + } + + private interface OnAlgorithmClickCallback { + void onAlgorithmClick(PwEncryptionAlgorithm algorithm); + } + + private class OnAlgorithmClickListener implements View.OnClickListener { + + private PwEncryptionAlgorithm algorithmClicked; + + public void setAlgorithm(PwEncryptionAlgorithm algorithm) { + this.algorithmClicked = algorithm; + } + + @Override + public void onClick(View view) { + if (onAlgorithmClickCallback != null) + onAlgorithmClickCallback.onAlgorithmClick(algorithmClicked); + algorithmSelected = algorithmClicked; + + // Close the dialog when an element is clicked + onDialogClosed(true); + dismiss(); + } + } + + public void setOnAlgorithmClickCallback(OnAlgorithmClickCallback onAlgorithmClickCallback) { + this.onAlgorithmClickCallback = onAlgorithmClickCallback; + } + + public PwEncryptionAlgorithm getAlgorithmSelected() { + return algorithmSelected; + } + + @Override + public void onDialogClosed(boolean positiveResult) { + if ( positiveResult ) { + assert getContext() != null; + + if (getAlgorithmSelected() != null) { + PwEncryptionAlgorithm newAlgorithm = getAlgorithmSelected(); + PwEncryptionAlgorithm oldAlgorithm = database.getEncryptionAlgorithm(); + database.assignDescription(newAlgorithm.getName(getResources())); + + Handler handler = new Handler(); + setAfterSaveDatabase(new AfterDescriptionSave(getContext(), handler, newAlgorithm, oldAlgorithm)); + } + } + + super.onDialogClosed(positiveResult); + } + + private class AfterDescriptionSave extends OnFinish { + + private PwEncryptionAlgorithm mNewAlgorithm; + private PwEncryptionAlgorithm mOldAlgorithm; + private Context mCtx; + + AfterDescriptionSave(Context ctx, Handler handler, PwEncryptionAlgorithm newAlgorithm, PwEncryptionAlgorithm oldAlgorithm) { + super(handler); + + mCtx = ctx; + mNewAlgorithm = newAlgorithm; + mOldAlgorithm = oldAlgorithm; + } + + @Override + public void run() { + PwEncryptionAlgorithm algorithmToShow = mNewAlgorithm; + + if (!mSuccess) { + displayMessage(mCtx); + database.assignEncryptionAlgorithm(mOldAlgorithm); + } + + getPreference().setSummary(algorithmToShow.getName(mCtx.getResources())); + + super.run(); + } + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/DatabaseDescriptionPreferenceDialogFragmentCompat.java b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/DatabaseDescriptionPreferenceDialogFragmentCompat.java index f72d500f8..73ece86c1 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/DatabaseDescriptionPreferenceDialogFragmentCompat.java +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/DatabaseDescriptionPreferenceDialogFragmentCompat.java @@ -7,7 +7,7 @@ import android.view.View; import com.kunzisoft.keepass.database.edit.OnFinish; -public class DatabaseDescriptionPreferenceDialogFragmentCompat extends DatabaseSavePreferenceDialogFragmentCompat { +public class DatabaseDescriptionPreferenceDialogFragmentCompat extends InputDatabaseSavePreferenceDialogFragmentCompat { public static DatabaseDescriptionPreferenceDialogFragmentCompat newInstance( String key) { @@ -32,12 +32,12 @@ public class DatabaseDescriptionPreferenceDialogFragmentCompat extends DatabaseS if ( positiveResult ) { assert getContext() != null; - String dbDescription = getInputText(); + String newDescription = getInputText(); String oldDescription = database.getDescription(); - database.assignDescription(dbDescription); + database.assignDescription(newDescription); Handler handler = new Handler(); - setAfterSaveDatabase(new AfterDescriptionSave(getContext(), handler, dbDescription, oldDescription)); + setAfterSaveDatabase(new AfterDescriptionSave(getContext(), handler, newDescription, oldDescription)); } super.onDialogClosed(positiveResult); @@ -64,7 +64,6 @@ public class DatabaseDescriptionPreferenceDialogFragmentCompat extends DatabaseS if (!mSuccess) { displayMessage(mCtx); database.assignDescription(mOldDescription); - database.assignDescription(mOldDescription); } getPreference().setSummary(descriptionToShow); diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/DatabaseNamePreferenceDialogFragmentCompat.java b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/DatabaseNamePreferenceDialogFragmentCompat.java index 59efc8350..602af572e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/DatabaseNamePreferenceDialogFragmentCompat.java +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/DatabaseNamePreferenceDialogFragmentCompat.java @@ -7,7 +7,7 @@ import android.view.View; import com.kunzisoft.keepass.database.edit.OnFinish; -public class DatabaseNamePreferenceDialogFragmentCompat extends DatabaseSavePreferenceDialogFragmentCompat { +public class DatabaseNamePreferenceDialogFragmentCompat extends InputDatabaseSavePreferenceDialogFragmentCompat { public static DatabaseNamePreferenceDialogFragmentCompat newInstance( String key) { @@ -32,12 +32,12 @@ public class DatabaseNamePreferenceDialogFragmentCompat extends DatabaseSavePref if ( positiveResult ) { assert getContext() != null; - String dbName = getInputText(); + String newName = getInputText(); String oldName = database.getName(); - database.assignName(dbName); + database.assignName(newName); Handler handler = new Handler(); - setAfterSaveDatabase(new AfterNameSave(getContext(), handler, dbName, oldName)); + setAfterSaveDatabase(new AfterNameSave(getContext(), handler, newName, oldName)); } super.onDialogClosed(positiveResult); diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/InputDatabaseSavePreferenceDialogFragmentCompat.java b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/InputDatabaseSavePreferenceDialogFragmentCompat.java new file mode 100644 index 000000000..d0d61b869 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/InputDatabaseSavePreferenceDialogFragmentCompat.java @@ -0,0 +1,29 @@ +package com.kunzisoft.keepass.settings.preferenceDialogFragment; + +import android.view.View; +import android.widget.EditText; + +import com.kunzisoft.keepass.R; + +public class InputDatabaseSavePreferenceDialogFragmentCompat extends DatabaseSavePreferenceDialogFragmentCompat { + + private EditText inputTextView; + + @Override + protected void onBindDialogView(View view) { + super.onBindDialogView(view); + + inputTextView = view.findViewById(R.id.input_text); + } + + public String getInputText() { + return this.inputTextView.getText().toString(); + } + + public void setInputText(String inputText) { + if (inputTextView != null && inputText != null) { + this.inputTextView.setText(inputText); + this.inputTextView.setSelection(this.inputTextView.getText().length()); + } + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/InputPreferenceDialogFragmentCompat.java b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/InputPreferenceDialogFragmentCompat.java index cfa326485..855b75a06 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/InputPreferenceDialogFragmentCompat.java +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/InputPreferenceDialogFragmentCompat.java @@ -2,33 +2,21 @@ package com.kunzisoft.keepass.settings.preferenceDialogFragment; import android.support.v7.preference.PreferenceDialogFragmentCompat; import android.view.View; -import android.widget.EditText; import android.widget.TextView; import com.kunzisoft.keepass.R; public abstract class InputPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat { - private EditText inputTextView; private TextView textExplanationView; @Override protected void onBindDialogView(View view) { super.onBindDialogView(view); - inputTextView = view.findViewById(R.id.input_text); textExplanationView = view.findViewById(R.id.explanation_text); } - public String getInputText() { - return this.inputTextView.getText().toString(); - } - - public void setInputText(String inputText) { - this.inputTextView.setText(inputText); - this.inputTextView.setSelection(this.inputTextView.getText().length()); - } - public String getExplanationText() { if (textExplanationView != null) return textExplanationView.getText().toString(); @@ -37,7 +25,7 @@ public abstract class InputPreferenceDialogFragmentCompat extends PreferenceDial } public void setExplanationText(String explanationText) { - if (textExplanationView != null) + if (textExplanationView != null && explanationText != null) this.textExplanationView.setText(explanationText); } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/RoundsFixPreferenceDialogFragmentCompat.java b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/RoundsFixPreferenceDialogFragmentCompat.java index d5e71a943..99c93d006 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/RoundsFixPreferenceDialogFragmentCompat.java +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/RoundsFixPreferenceDialogFragmentCompat.java @@ -22,6 +22,7 @@ package com.kunzisoft.keepass.settings.preferenceDialogFragment; import android.os.Bundle; import android.support.v7.preference.DialogPreference; import android.view.View; +import android.widget.EditText; import android.widget.Toast; import com.kunzisoft.keepass.R; @@ -29,6 +30,8 @@ import com.kunzisoft.keepass.settings.preference.RoundsPreference; public class RoundsFixPreferenceDialogFragmentCompat extends InputPreferenceDialogFragmentCompat { + private EditText inputTextView; + public static RoundsFixPreferenceDialogFragmentCompat newInstance( String key) { final RoundsFixPreferenceDialogFragmentCompat @@ -44,6 +47,8 @@ public class RoundsFixPreferenceDialogFragmentCompat extends InputPreferenceDial protected void onBindDialogView(View view) { super.onBindDialogView(view); + inputTextView = view.findViewById(R.id.input_text); + DialogPreference preference = getPreference(); if (preference instanceof RoundsPreference) { setExplanationText(((RoundsPreference) preference).getExplanation()); @@ -75,4 +80,13 @@ public class RoundsFixPreferenceDialogFragmentCompat extends InputPreferenceDial } } } + + public String getInputText() { + return this.inputTextView.getText().toString(); + } + + public void setInputText(String inputText) { + this.inputTextView.setText(inputText); + this.inputTextView.setSelection(this.inputTextView.getText().length()); + } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/RoundsPreferenceDialogFragmentCompat.java b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/RoundsPreferenceDialogFragmentCompat.java index afc861034..5640d77ef 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/RoundsPreferenceDialogFragmentCompat.java +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferenceDialogFragment/RoundsPreferenceDialogFragmentCompat.java @@ -28,7 +28,7 @@ import android.widget.Toast; import com.kunzisoft.keepass.R; import com.kunzisoft.keepass.database.edit.OnFinish; -public class RoundsPreferenceDialogFragmentCompat extends DatabaseSavePreferenceDialogFragmentCompat { +public class RoundsPreferenceDialogFragmentCompat extends InputDatabaseSavePreferenceDialogFragmentCompat { public static RoundsPreferenceDialogFragmentCompat newInstance( String key) { diff --git a/app/src/main/res/layout-v23/pref_dialog_list_explanation.xml b/app/src/main/res/layout-v23/pref_dialog_list_explanation.xml new file mode 100644 index 000000000..e89af7974 --- /dev/null +++ b/app/src/main/res/layout-v23/pref_dialog_list_explanation.xml @@ -0,0 +1,40 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pref_dialog_list_explanation.xml b/app/src/main/res/layout/pref_dialog_list_explanation.xml new file mode 100644 index 000000000..0f835c4fa --- /dev/null +++ b/app/src/main/res/layout/pref_dialog_list_explanation.xml @@ -0,0 +1,39 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pref_dialog_list_radio_item.xml b/app/src/main/res/layout/pref_dialog_list_radio_item.xml new file mode 100644 index 000000000..957cb4c2a --- /dev/null +++ b/app/src/main/res/layout/pref_dialog_list_radio_item.xml @@ -0,0 +1,20 @@ + + + + + \ No newline at end of file