diff --git a/app/build.gradle b/app/build.gradle
index 8d9385cdd..45d5d3c9f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -6,7 +6,7 @@ android {
defaultConfig {
applicationId "com.kunzisoft.keepass"
- minSdkVersion 15
+ minSdkVersion 14
targetSdkVersion 27
versionCode = 14
versionName = "2.5.0.0beta14"
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.java b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.java
index ef5da57ed..e77333e69 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.java
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryActivity.java
@@ -83,10 +83,11 @@ public class EntryActivity extends LockingHideActivity {
private ClipboardHelper clipboardHelper;
private boolean firstLaunchOfActivity;
- public static void launch(Activity act, PwEntry pw) {
+ public static void launch(Activity act, PwEntry pw, boolean readOnly) {
if (LockingActivity.checkTimeIsAllowedOrFinish(act)) {
Intent intent = new Intent(act, EntryActivity.class);
intent.putExtra(KEY_ENTRY, Types.UUIDtoBytes(pw.getUUID()));
+ ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
act.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
}
}
@@ -104,13 +105,15 @@ public class EntryActivity extends LockingHideActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
+ readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, getIntent());
+
Database db = App.getDB();
// Likely the app has been killed exit the activity
if ( ! db.getLoaded() ) {
finish();
return;
}
- readOnly = db.isReadOnly();
+ readOnly = db.isReadOnly() || readOnly;
mShowPassword = !PreferencesUtil.isPasswordMask(this);
@@ -230,6 +233,12 @@ public class EntryActivity extends LockingHideActivity {
firstLaunchOfActivity = false;
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
+ super.onSaveInstanceState(outState);
+ }
+
/**
* Check and display learning views
* Displays the explanation for copying a field and editing an entry
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java
index d90d8d8bf..4ccbfe586 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java
@@ -80,6 +80,8 @@ import com.kunzisoft.keepass.view.AddNodeButtonView;
import net.cachapa.expandablelayout.ExpandableLayout;
+import static com.kunzisoft.keepass.activities.ReadOnlyHelper.READ_ONLY_DEFAULT;
+
public class GroupActivity extends ListNodesActivity
implements GroupEditDialogFragment.EditGroupListener,
IconPickerDialogFragment.IconPickerListener,
@@ -88,6 +90,8 @@ public class GroupActivity extends ListNodesActivity
private static final String TAG = GroupActivity.class.getName();
+ protected static final String GROUP_ID_KEY = "GROUP_ID_KEY";
+
private Toolbar toolbar;
private ExpandableLayout toolbarPasteExpandableLayout;
@@ -99,7 +103,6 @@ public class GroupActivity extends ListNodesActivity
protected boolean addGroupEnabled = false;
protected boolean addEntryEnabled = false;
protected boolean isRoot = false;
- protected boolean readOnly = false;
private static final String OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY";
private static final String NODE_TO_COPY_KEY = "NODE_TO_COPY_KEY";
@@ -107,64 +110,67 @@ public class GroupActivity extends ListNodesActivity
private PwGroup oldGroupToUpdate;
private PwNode nodeToCopy;
private PwNode nodeToMove;
-
- public static void launch(Activity act) {
+
+ // After a database creation
+ public static void launch(Activity act) {
+ launch(act, READ_ONLY_DEFAULT);
+ }
+
+ public static void launch(Activity act, boolean readOnly) {
startRecordTime(act);
- launch(act, null);
+ launch(act, null, readOnly);
}
- public static void launch(Activity act, PwGroup group) {
- if (checkTimeIsAllowedOrFinish(act)) {
- Intent intent = new Intent(act, GroupActivity.class);
+ private static void buildAndLaunchIntent(Activity activity, PwGroup group, boolean readOnly,
+ IntentBuildLauncher intentBuildLauncher) {
+ if (checkTimeIsAllowedOrFinish(activity)) {
+ Intent intent = new Intent(activity, GroupActivity.class);
if (group != null) {
intent.putExtra(GROUP_ID_KEY, group.getId());
}
- act.startActivityForResult(intent, 0);
+ ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
+ intentBuildLauncher.startActivityForResult(intent);
}
}
- public static void launchForKeyboardResult(Activity act) {
+ public static void launch(Activity activity, PwGroup group, boolean readOnly) {
+ buildAndLaunchIntent(activity, group, readOnly,
+ (intent) -> activity.startActivityForResult(intent, 0));
+ }
+
+ public static void launchForKeyboardResult(Activity act, boolean readOnly) {
startRecordTime(act);
- launchForKeyboardResult(act, null);
+ launchForKeyboardResult(act, null, readOnly);
}
- public static void launchForKeyboardResult(Activity act, PwGroup group) {
- // TODO remove
- if (checkTimeIsAllowedOrFinish(act)) {
- Intent intent = new Intent(act, GroupActivity.class);
- if (group != null) {
- intent.putExtra(GROUP_ID_KEY, group.getId());
- }
+ public static void launchForKeyboardResult(Activity activity, PwGroup group, boolean readOnly) {
+ // TODO implement pre search to directly open the direct group
+ buildAndLaunchIntent(activity, group, readOnly, (intent) -> {
EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent);
- act.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
- }
+ activity.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
+ });
}
-
@RequiresApi(api = Build.VERSION_CODES.O)
- public static void launchForAutofillResult(Activity act, AssistStructure assistStructure) {
+ public static void launchForAutofillResult(Activity act, AssistStructure assistStructure, boolean readOnly) {
if ( assistStructure != null ) {
startRecordTime(act);
- launchForAutofillResult(act, null, assistStructure);
+ launchForAutofillResult(act, null, assistStructure, readOnly);
} else {
- launch(act);
+ launch(act, readOnly);
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
- public static void launchForAutofillResult(Activity act, PwGroup group, AssistStructure assistStructure) {
- // TODO remove
+ public static void launchForAutofillResult(Activity activity, PwGroup group, AssistStructure assistStructure, boolean readOnly) {
+ // TODO implement pre search to directly open the direct group
if ( assistStructure != null ) {
- if (checkTimeIsAllowedOrFinish(act)) {
- Intent intent = new Intent(act, GroupActivity.class);
- if (group != null) {
- intent.putExtra(GROUP_ID_KEY, group.getId());
- }
+ buildAndLaunchIntent(activity, group, readOnly, (intent) -> {
AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
- act.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
- }
+ activity.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
+ });
} else {
- launch(act, group);
+ launch(activity, group, readOnly);
}
}
@@ -255,7 +261,7 @@ public class GroupActivity extends ListNodesActivity
}
Database db = App.getDB();
- readOnly = db.isReadOnly();
+ readOnly = db.isReadOnly() || readOnly; // Force read only if the database is like that
PwGroup root = db.getPwDatabase().getRootGroup();
Log.w(TAG, "Creating tree view");
@@ -268,7 +274,7 @@ public class GroupActivity extends ListNodesActivity
if (currentGroup != null) {
addGroupEnabled = !readOnly;
- addEntryEnabled = !readOnly; // TODO consultation mode
+ addEntryEnabled = !readOnly;
isRoot = (currentGroup == root);
if (!currentGroup.allowAddEntryIfIsRoot())
addEntryEnabled = !isRoot && addEntryEnabled;
@@ -496,7 +502,8 @@ public class GroupActivity extends ListNodesActivity
// If no node, show education to add new one
if (listNodesFragment != null
&& listNodesFragment.isEmpty()) {
- if (!PreferencesUtil.isEducationNewNodePerformed(this)) {
+ if (!PreferencesUtil.isEducationNewNodePerformed(this)
+ && addNodeButtonView.isVisible()) {
TapTargetView.showFor(this,
TapTarget.forView(findViewById(R.id.add_button),
@@ -633,7 +640,8 @@ public class GroupActivity extends ListNodesActivity
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.search, menu);
- inflater.inflate(R.menu.database_master_key, menu);
+ if (!readOnly)
+ inflater.inflate(R.menu.database_master_key, menu);
inflater.inflate(R.menu.database_lock, menu);
// Get the SearchView and set the searchable configuration
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/IntentBuildLauncher.java b/app/src/main/java/com/kunzisoft/keepass/activities/IntentBuildLauncher.java
new file mode 100644
index 000000000..f7eddb8be
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/IntentBuildLauncher.java
@@ -0,0 +1,7 @@
+package com.kunzisoft.keepass.activities;
+
+import android.content.Intent;
+
+public interface IntentBuildLauncher {
+ void startActivityForResult(Intent intent);
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesActivity.java b/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesActivity.java
index 6779949c7..3d3897433 100644
--- a/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesActivity.java
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesActivity.java
@@ -41,9 +41,9 @@ import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
-import com.kunzisoft.keepass.selection.EntrySelectionHelper;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.password.AssignPasswordHelper;
+import com.kunzisoft.keepass.selection.EntrySelectionHelper;
import com.kunzisoft.keepass.utils.MenuUtil;
public abstract class ListNodesActivity extends LockingActivity
@@ -51,14 +51,14 @@ public abstract class ListNodesActivity extends LockingActivity
NodeAdapter.NodeClickCallback,
SortDialogFragment.SortSelectionListener {
- protected static final String GROUP_ID_KEY = "GROUP_ID_KEY";
-
protected static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
protected ListNodesFragment listNodesFragment;
protected PwGroup mCurrentGroup;
protected TextView groupNameView;
+ protected boolean readOnly;
+
protected boolean entrySelectionMode;
protected AutofillHelper autofillHelper;
@@ -76,6 +76,8 @@ public abstract class ListNodesActivity extends LockingActivity
return;
}
+ readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, getIntent());
+
invalidateOptionsMenu();
mCurrentGroup = retrieveCurrentGroup(savedInstanceState);
@@ -89,6 +91,12 @@ public abstract class ListNodesActivity extends LockingActivity
}
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
+ super.onSaveInstanceState(outState);
+ }
+
protected abstract PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState);
@Override
@@ -151,7 +159,7 @@ public abstract class ListNodesActivity extends LockingActivity
switch ( item.getItemId() ) {
default:
// Check the time lock before launching settings
- MenuUtil.onDefaultMenuOptionsItemSelected(this, item, true);
+ MenuUtil.onDefaultMenuOptionsItemSelected(this, item, readOnly, true);
return super.onOptionsItemSelected(item);
}
}
@@ -193,7 +201,7 @@ public abstract class ListNodesActivity extends LockingActivity
openGroup((PwGroup) node);
break;
case ENTRY:
- EntryActivity.launch(this, (PwEntry) node);
+ EntryActivity.launch(this, (PwEntry) node, readOnly);
break;
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/ReadOnlyHelper.java b/app/src/main/java/com/kunzisoft/keepass/activities/ReadOnlyHelper.java
new file mode 100644
index 000000000..78164c69a
--- /dev/null
+++ b/app/src/main/java/com/kunzisoft/keepass/activities/ReadOnlyHelper.java
@@ -0,0 +1,61 @@
+package com.kunzisoft.keepass.activities;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import com.kunzisoft.keepass.settings.PreferencesUtil;
+
+public class ReadOnlyHelper {
+
+ public static final String READ_ONLY_KEY = "READ_ONLY_KEY";
+
+ public static final boolean READ_ONLY_DEFAULT = false;
+
+ public static boolean retrieveReadOnlyFromInstanceStateOrPreference(Context context, Bundle savedInstanceState) {
+ boolean readOnly;
+ if (savedInstanceState != null
+ && savedInstanceState.containsKey(READ_ONLY_KEY)) {
+ readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
+ } else {
+ readOnly = PreferencesUtil.enableReadOnlyDatabase(context);
+ }
+ return readOnly;
+ }
+
+ public static boolean retrieveReadOnlyFromInstanceStateOrArguments(Bundle savedInstanceState, Bundle arguments) {
+ boolean readOnly = READ_ONLY_DEFAULT;
+ if (savedInstanceState != null
+ && savedInstanceState.containsKey(READ_ONLY_KEY)) {
+ readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
+ } else if (arguments != null
+ && arguments.containsKey(READ_ONLY_KEY)) {
+ readOnly = arguments.getBoolean(READ_ONLY_KEY);
+ }
+ return readOnly;
+ }
+
+ public static boolean retrieveReadOnlyFromInstanceStateOrIntent(Bundle savedInstanceState, Intent intent) {
+ boolean readOnly = READ_ONLY_DEFAULT;
+ if (savedInstanceState != null
+ && savedInstanceState.containsKey(READ_ONLY_KEY)) {
+ readOnly = savedInstanceState.getBoolean(READ_ONLY_KEY);
+ } else {
+ if (intent != null)
+ readOnly = intent.getBooleanExtra(READ_ONLY_KEY, READ_ONLY_DEFAULT);
+ }
+ return readOnly;
+ }
+
+ public static void putReadOnlyInIntent(Intent intent, boolean readOnly) {
+ intent.putExtra(READ_ONLY_KEY, readOnly);
+ }
+
+ public static void putReadOnlyInBundle(Bundle bundle, boolean readOnly) {
+ bundle.putBoolean(READ_ONLY_KEY, readOnly);
+ }
+
+ public static void onSaveInstanceState(Bundle outState, boolean readOnly) {
+ outState.putBoolean(READ_ONLY_KEY, readOnly);
+ }
+}
diff --git a/app/src/main/java/com/kunzisoft/keepass/password/PasswordActivity.java b/app/src/main/java/com/kunzisoft/keepass/password/PasswordActivity.java
index 249977583..12ebc0153 100644
--- a/app/src/main/java/com/kunzisoft/keepass/password/PasswordActivity.java
+++ b/app/src/main/java/com/kunzisoft/keepass/password/PasswordActivity.java
@@ -55,9 +55,9 @@ import android.widget.Toast;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.kunzisoft.keepass.R;
+import com.kunzisoft.keepass.activities.ReadOnlyHelper;
import com.kunzisoft.keepass.activities.GroupActivity;
-import com.kunzisoft.keepass.selection.EntrySelectionHelper;
-import com.kunzisoft.keepass.lock.LockingActivity;
+import com.kunzisoft.keepass.activities.IntentBuildLauncher;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.autofill.AutofillHelper;
import com.kunzisoft.keepass.compat.ClipDataCompat;
@@ -69,6 +69,8 @@ import com.kunzisoft.keepass.fileselect.KeyFileHelper;
import com.kunzisoft.keepass.fingerprint.FingerPrintAnimatedVector;
import com.kunzisoft.keepass.fingerprint.FingerPrintExplanationDialog;
import com.kunzisoft.keepass.fingerprint.FingerPrintHelper;
+import com.kunzisoft.keepass.lock.LockingActivity;
+import com.kunzisoft.keepass.selection.EntrySelectionHelper;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.stylish.StylishActivity;
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment;
@@ -124,6 +126,8 @@ public class PasswordActivity extends StylishActivity
private CompoundButton checkboxDefaultDatabaseView;
private CompoundButton.OnCheckedChangeListener enableButtonOncheckedChangeListener;
+ private boolean readOnly;
+
private DefaultCheckChange defaultCheckChange;
private ValidateButtonViewClickListener validateButtonViewClickListener;
@@ -139,16 +143,23 @@ public class PasswordActivity extends StylishActivity
}
public static void launch(
- Activity act,
+ Activity activity,
String fileName,
String keyFile) throws FileNotFoundException {
verifyFileNameUriFromLaunch(fileName);
- Intent intent = new Intent(act, PasswordActivity.class);
+ buildAndLaunchIntent(activity, fileName, keyFile, (intent) -> {
+ // only to avoid visible flickering when redirecting
+ activity.startActivityForResult(intent, RESULT_CANCELED);
+ });
+ }
+
+ private static void buildAndLaunchIntent(Activity activity, String fileName, String keyFile,
+ IntentBuildLauncher intentBuildLauncher) {
+ Intent intent = new Intent(activity, PasswordActivity.class);
intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName);
intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile);
- // only to avoid visible flickering when redirecting
- act.startActivityForResult(intent, RESULT_CANCELED);
+ intentBuildLauncher.startActivityForResult(intent);
}
public static void launchForKeyboardResult(
@@ -158,17 +169,16 @@ public class PasswordActivity extends StylishActivity
}
public static void launchForKeyboardResult(
- Activity act,
+ Activity activity,
String fileName,
String keyFile) throws FileNotFoundException {
verifyFileNameUriFromLaunch(fileName);
- Intent intent = new Intent(act, PasswordActivity.class);
- intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName);
- intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile);
- EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent);
- // only to avoid visible flickering when redirecting
- act.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
+ buildAndLaunchIntent(activity, fileName, keyFile, (intent) -> {
+ EntrySelectionHelper.addEntrySelectionModeExtraInIntent(intent);
+ // only to avoid visible flickering when redirecting
+ activity.startActivityForResult(intent, EntrySelectionHelper.ENTRY_SELECTION_RESPONSE_REQUEST_CODE);
+ });
}
@RequiresApi(api = Build.VERSION_CODES.O)
@@ -181,20 +191,19 @@ public class PasswordActivity extends StylishActivity
@RequiresApi(api = Build.VERSION_CODES.O)
public static void launchForAutofillResult(
- Activity act,
+ Activity activity,
String fileName,
String keyFile,
AssistStructure assistStructure) throws FileNotFoundException {
verifyFileNameUriFromLaunch(fileName);
if ( assistStructure != null ) {
- Intent intent = new Intent(act, PasswordActivity.class);
- intent.putExtra(UriIntentInitTask.KEY_FILENAME, fileName);
- intent.putExtra(UriIntentInitTask.KEY_KEYFILE, keyFile);
- AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
- act.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
+ buildAndLaunchIntent(activity, fileName, keyFile, (intent) -> {
+ AutofillHelper.addAssistStructureExtraInIntent(intent, assistStructure);
+ activity.startActivityForResult(intent, AutofillHelper.AUTOFILL_RESPONSE_REQUEST_CODE);
+ });
} else {
- launch(act, fileName, keyFile);
+ launch(activity, fileName, keyFile);
}
}
@@ -276,6 +285,8 @@ public class PasswordActivity extends StylishActivity
checkboxKeyfileView = findViewById(R.id.keyfile_checkox);
checkboxDefaultDatabaseView = findViewById(R.id.default_database);
+ readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState);
+
View browseView = findViewById(R.id.browse_button);
keyFileHelper = new KeyFileHelper(PasswordActivity.this);
browseView.setOnClickListener(keyFileHelper.getOpenFileOnClickViewListener());
@@ -326,8 +337,6 @@ public class PasswordActivity extends StylishActivity
autofillHelper = new AutofillHelper();
autofillHelper.retrieveAssistStructure(getIntent());
}
-
- checkAndPerformedEducation();
}
@Override
@@ -380,11 +389,17 @@ public class PasswordActivity extends StylishActivity
.execute(getIntent());
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
+ super.onSaveInstanceState(outState);
+ }
+
/**
* Check and display learning views
* Displays the explanation for a database opening with fingerprints if available
*/
- private void checkAndPerformedEducation() {
+ private void checkAndPerformedEducation(Menu menu) {
if (PreferencesUtil.isEducationScreensEnabled(this)) {
if (!PreferencesUtil.isEducationUnlockPerformed(this)) {
@@ -414,7 +429,39 @@ public class PasswordActivity extends StylishActivity
}
});
// TODO make a period for donation
- PreferencesUtil.saveEducationPreference(PasswordActivity.this, R.string.education_unlock_key);
+ PreferencesUtil.saveEducationPreference(PasswordActivity.this,
+ R.string.education_unlock_key);
+
+ } else if (!PreferencesUtil.isEducationReadOnlyPerformed(this)) {
+
+ try {
+ TapTargetView.showFor(this,
+ TapTarget.forToolbarMenuItem(toolbar, R.id.menu_open_file_read_mode_key,
+ getString(R.string.education_read_only_title),
+ getString(R.string.education_read_only_summary))
+ .textColorInt(Color.WHITE)
+ .tintTarget(true)
+ .cancelable(true),
+ new TapTargetView.Listener() {
+ @Override
+ public void onTargetClick(TapTargetView view) {
+ super.onTargetClick(view);
+ MenuItem editItem = menu.findItem(R.id.menu_open_file_read_mode_key);
+ onOptionsItemSelected(editItem);
+ }
+
+ @Override
+ public void onOuterCircleClick(TapTargetView view) {
+ super.onOuterCircleClick(view);
+ view.dismiss(false);
+ }
+ });
+ PreferencesUtil.saveEducationPreference(this,
+ R.string.education_read_only_key);
+ } catch (Exception e) {
+ // If icon not visible
+ Log.w(TAG, "Can't performed education for entry's edition");
+ }
}
}
}
@@ -953,14 +1000,14 @@ public class PasswordActivity extends StylishActivity
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
assistStructure = autofillHelper.getAssistStructure();
if (assistStructure != null) {
- GroupActivity.launchForAutofillResult(PasswordActivity.this, assistStructure);
+ GroupActivity.launchForAutofillResult(PasswordActivity.this, assistStructure, readOnly);
}
}
if (assistStructure == null) {
if (entrySelectionMode) {
- GroupActivity.launchForKeyboardResult(PasswordActivity.this);
+ GroupActivity.launchForKeyboardResult(PasswordActivity.this, readOnly);
} else {
- GroupActivity.launch(PasswordActivity.this);
+ GroupActivity.launch(PasswordActivity.this, readOnly);
}
}
}
@@ -968,16 +1015,35 @@ public class PasswordActivity extends StylishActivity
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
+ // Read menu
+ inflater.inflate(R.menu.open_file, menu);
+ changeOpenFileReadIcon(menu.findItem(R.id.menu_open_file_read_mode_key));
+
MenuUtil.defaultMenuInflater(inflater, menu);
+
+ // Fingerprint menu
if (!fingerprintMustBeConfigured
&& prefsNoBackup.contains(getPreferenceKeyValue()) )
inflater.inflate(R.menu.fingerprint, menu);
super.onCreateOptionsMenu(menu);
+ // Show education views
+ new Handler().post(() -> checkAndPerformedEducation(menu));
+
return true;
}
+ private void changeOpenFileReadIcon(MenuItem togglePassword) {
+ if ( readOnly ) {
+ togglePassword.setTitle(R.string.menu_file_selection_read_only);
+ togglePassword.setIcon(R.drawable.ic_read_only_white_24dp);
+ } else {
+ togglePassword.setTitle(R.string.menu_open_file_read_and_write);
+ togglePassword.setIcon(R.drawable.ic_read_write_white_24dp);
+ }
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
@@ -985,6 +1051,10 @@ public class PasswordActivity extends StylishActivity
case android.R.id.home:
finish();
break;
+ case R.id.menu_open_file_read_mode_key:
+ readOnly = !readOnly;
+ changeOpenFileReadIcon(item);
+ break;
case R.id.menu_fingerprint_remove_key:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
deleteEntryKey();
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 0f7595818..06ef0a21e 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.java
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.java
@@ -43,6 +43,7 @@ import android.widget.Toast;
import com.kunzisoft.keepass.BuildConfig;
import com.kunzisoft.keepass.R;
+import com.kunzisoft.keepass.activities.ReadOnlyHelper;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.dialogs.ProFeatureDialogFragment;
@@ -72,6 +73,9 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
private static final int REQUEST_CODE_AUTOFILL = 5201;
+ private Database database;
+ private boolean databaseReadOnly;
+
private int count = 0;
private Preference roundPref;
@@ -79,14 +83,24 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
private Preference parallelismPref;
public static NestedSettingsFragment newInstance(Screen key) {
+ return newInstance(key, ReadOnlyHelper.READ_ONLY_DEFAULT);
+ }
+
+ public static NestedSettingsFragment newInstance(Screen key, boolean databaseReadOnly) {
NestedSettingsFragment fragment = new NestedSettingsFragment();
// supply arguments to bundle.
Bundle args = new Bundle();
args.putInt(TAG_KEY, key.ordinal());
+ ReadOnlyHelper.putReadOnlyInBundle(args, databaseReadOnly);
fragment.setArguments(args);
return fragment;
}
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
@Override
public void onResume() {
super.onResume();
@@ -110,6 +124,10 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
if (getArguments() != null)
key = getArguments().getInt(TAG_KEY);
+ database = App.getDB();
+ databaseReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, getArguments());
+ databaseReadOnly = database.isReadOnly() || databaseReadOnly;
+
// Load the preferences from an XML resource
switch (Screen.values()[key]) {
case APPLICATION:
@@ -306,23 +324,22 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
case DATABASE:
setPreferencesFromResource(R.xml.database_preferences, rootKey);
- Database db = App.getDB();
- if (db.getLoaded()) {
+ if (database.getLoaded()) {
PreferenceCategory dbGeneralPrefCategory = (PreferenceCategory) findPreference(getString(R.string.database_general_key));
// Db name
Preference dbNamePref = findPreference(getString(R.string.database_name_key));
- if ( db.containsName() ) {
- dbNamePref.setSummary(db.getName());
+ if ( database.containsName() ) {
+ dbNamePref.setSummary(database.getName());
} else {
dbGeneralPrefCategory.removePreference(dbNamePref);
}
// Db description
Preference dbDescriptionPref = findPreference(getString(R.string.database_description_key));
- if ( db.containsDescription() ) {
- dbDescriptionPref.setSummary(db.getDescription());
+ if ( database.containsDescription() ) {
+ dbDescriptionPref.setSummary(database.getDescription());
} else {
dbGeneralPrefCategory.removePreference(dbDescriptionPref);
}
@@ -331,9 +348,9 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
SwitchPreference recycleBinPref = (SwitchPreference) findPreference(getString(R.string.recycle_bin_key));
// TODO Recycle
dbGeneralPrefCategory.removePreference(recycleBinPref); // To delete
- if (db.isRecycleBinAvailable()) {
+ if (database.isRecycleBinAvailable()) {
- recycleBinPref.setChecked(db.isRecycleBinEnabled());
+ recycleBinPref.setChecked(database.isRecycleBinEnabled());
recycleBinPref.setEnabled(false);
} else {
dbGeneralPrefCategory.removePreference(recycleBinPref);
@@ -341,27 +358,27 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
// Version
Preference dbVersionPref = findPreference(getString(R.string.database_version_key));
- dbVersionPref.setSummary(db.getVersion());
+ dbVersionPref.setSummary(database.getVersion());
// Encryption Algorithm
Preference algorithmPref = findPreference(getString(R.string.encryption_algorithm_key));
- algorithmPref.setSummary(db.getEncryptionAlgorithmName(getResources()));
+ algorithmPref.setSummary(database.getEncryptionAlgorithmName(getResources()));
// Key derivation function
Preference kdfPref = findPreference(getString(R.string.key_derivation_function_key));
- kdfPref.setSummary(db.getKeyDerivationName(getResources()));
+ kdfPref.setSummary(database.getKeyDerivationName(getResources()));
// Round encryption
roundPref = findPreference(getString(R.string.transform_rounds_key));
- roundPref.setSummary(db.getNumberKeyEncryptionRoundsAsString());
+ roundPref.setSummary(database.getNumberKeyEncryptionRoundsAsString());
// Memory Usage
memoryPref = findPreference(getString(R.string.memory_usage_key));
- memoryPref.setSummary(db.getMemoryUsageAsString());
+ memoryPref.setSummary(database.getMemoryUsageAsString());
// Parallelism
parallelismPref = findPreference(getString(R.string.parallelism_key));
- parallelismPref.setSummary(db.getParallelismAsString());
+ parallelismPref.setSummary(database.getParallelismAsString());
} else {
Log.e(getClass().getName(), "Database isn't ready");
@@ -480,18 +497,16 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
assert getFragmentManager() != null;
- DialogFragment dialogFragment = null;
+ boolean otherDialogFragment = false;
+ DialogFragment dialogFragment = null;
if (preference.getKey().equals(getString(R.string.database_name_key))) {
dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.getKey());
- }
- else if (preference.getKey().equals(getString(R.string.database_description_key))) {
+ } else if (preference.getKey().equals(getString(R.string.database_description_key))) {
dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.getKey());
- }
- else if (preference.getKey().equals(getString(R.string.encryption_algorithm_key))) {
+ } else if (preference.getKey().equals(getString(R.string.encryption_algorithm_key))) {
dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.getKey());
- }
- else if (preference.getKey().equals(getString(R.string.key_derivation_function_key))) {
+ } else if (preference.getKey().equals(getString(R.string.key_derivation_function_key))) {
DatabaseKeyDerivationPreferenceDialogFragmentCompat keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.getKey());
// Add other prefs to manage
if (roundPref != null)
@@ -501,24 +516,23 @@ public class NestedSettingsFragment extends PreferenceFragmentCompat
if (parallelismPref != null)
keyDerivationDialogFragment.setParallelismPreference(parallelismPref);
dialogFragment = keyDerivationDialogFragment;
- }
- else if (preference.getKey().equals(getString(R.string.transform_rounds_key))) {
+ } else if (preference.getKey().equals(getString(R.string.transform_rounds_key))) {
dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.getKey());
- }
- else if (preference.getKey().equals(getString(R.string.memory_usage_key))) {
+ } else if (preference.getKey().equals(getString(R.string.memory_usage_key))) {
dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.getKey());
- }
- else if (preference.getKey().equals(getString(R.string.parallelism_key))) {
+ } else if (preference.getKey().equals(getString(R.string.parallelism_key))) {
dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.getKey());
+ } else {
+ otherDialogFragment = true;
}
- if (dialogFragment != null) {
+ if (dialogFragment != null && !databaseReadOnly) {
dialogFragment.setTargetFragment(this, 0);
dialogFragment.show(getFragmentManager(), null);
}
// Could not be handled here. Try with the super method.
- else {
+ else if (otherDialogFragment) {
super.onDisplayPreferenceDialog(preference);
}
}
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.java b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.java
index 932b2d1f8..904c648ae 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.java
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.java
@@ -158,6 +158,12 @@ public class PreferencesUtil {
context.getResources().getBoolean(R.bool.allow_no_password_default));
}
+ public static boolean enableReadOnlyDatabase(Context context) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ return prefs.getBoolean(context.getString(R.string.enable_read_only_key),
+ context.getResources().getBoolean(R.bool.enable_read_only_default));
+ }
+
/**
* All preference keys associated with education
*/
@@ -166,6 +172,7 @@ public class PreferencesUtil {
R.string.education_select_db_key,
R.string.education_open_link_db_key,
R.string.education_unlock_key,
+ R.string.education_read_only_key,
R.string.education_search_key,
R.string.education_new_node_key,
R.string.education_sort_key,
@@ -245,6 +252,18 @@ public class PreferencesUtil {
context.getResources().getBoolean(R.bool.education_unlock_default));
}
+ /**
+ * Determines whether the explanatory view of the database read-only has already been displayed.
+ *
+ * @param context The context to open the SharedPreferences
+ * @return boolean value of education_read_only_key key
+ */
+ public static boolean isEducationReadOnlyPerformed(Context context) {
+ SharedPreferences prefs = getEducationSharedPreferences(context);
+ return prefs.getBoolean(context.getString(R.string.education_read_only_key),
+ context.getResources().getBoolean(R.bool.education_read_only_default));
+ }
+
/**
* Determines whether the explanatory view of search has already been displayed.
*
diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.java b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.java
index ef61f9dbc..2b3db66ce 100644
--- a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.java
+++ b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.java
@@ -28,6 +28,7 @@ import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import com.kunzisoft.keepass.R;
+import com.kunzisoft.keepass.activities.ReadOnlyHelper;
import com.kunzisoft.keepass.lock.LockingActivity;
@@ -39,17 +40,20 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
private Toolbar toolbar;
- public static void launch(Activity activity) {
- Intent i = new Intent(activity, SettingsActivity.class);
- activity.startActivity(i);
+ private boolean readOnly;
+
+ public static void launch(Activity activity, boolean readOnly) {
+ Intent intent = new Intent(activity, SettingsActivity.class);
+ ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly);
+ activity.startActivity(intent);
}
- public static void launch(Activity activity, boolean checkLock) {
+ public static void launch(Activity activity, boolean readOnly, boolean checkLock) {
// To avoid flickering when launch settings in a LockingActivity
if (!checkLock)
- launch(activity);
+ launch(activity, readOnly);
else if (LockingActivity.checkTimeIsAllowedOrFinish(activity)) {
- launch(activity);
+ launch(activity, readOnly);
}
}
@@ -72,6 +76,8 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, getIntent());
+
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, retrieveMainFragment())
@@ -81,6 +87,12 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
backupManager = new BackupManager(this);
}
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
+ super.onSaveInstanceState(outState);
+ }
+
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
@@ -114,7 +126,7 @@ public class SettingsActivity extends LockingActivity implements MainPreferenceF
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
R.anim.slide_in_left, R.anim.slide_out_right)
- .replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key), TAG_NESTED)
+ .replace(R.id.fragment_container, NestedSettingsFragment.newInstance(key, readOnly), TAG_NESTED)
.addToBackStack(TAG_NESTED)
.commit();
diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.java b/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.java
index eb372fd91..a5ad75db8 100644
--- a/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.java
+++ b/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.java
@@ -32,6 +32,8 @@ import com.kunzisoft.keepass.activities.AboutActivity;
import com.kunzisoft.keepass.settings.SettingsActivity;
import com.kunzisoft.keepass.stylish.StylishActivity;
+import static com.kunzisoft.keepass.activities.ReadOnlyHelper.READ_ONLY_DEFAULT;
+
public class MenuUtil {
@@ -56,20 +58,20 @@ public class MenuUtil {
}
public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item) {
- return onDefaultMenuOptionsItemSelected(activity, item, false);
+ return onDefaultMenuOptionsItemSelected(activity, item, READ_ONLY_DEFAULT, false);
}
/*
* @param checkLock Check the time lock before launch settings in LockingActivity
*/
- public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item, boolean checkLock) {
+ public static boolean onDefaultMenuOptionsItemSelected(StylishActivity activity, MenuItem item, boolean readOnly, boolean checkLock) {
switch (item.getItemId()) {
case R.id.menu_contribute:
return onContributionItemSelected(activity);
case R.id.menu_app_settings:
// To avoid flickering when launch settings in a LockingActivity
- SettingsActivity.launch(activity, checkLock);
+ SettingsActivity.launch(activity, readOnly, checkLock);
return true;
case R.id.menu_about:
diff --git a/app/src/main/java/com/kunzisoft/keepass/view/AddNodeButtonView.java b/app/src/main/java/com/kunzisoft/keepass/view/AddNodeButtonView.java
index 7bcc65caf..b138b8da7 100644
--- a/app/src/main/java/com/kunzisoft/keepass/view/AddNodeButtonView.java
+++ b/app/src/main/java/com/kunzisoft/keepass/view/AddNodeButtonView.java
@@ -167,6 +167,7 @@ public class AddNodeButtonView extends RelativeLayout {
this.addEntryEnable = enable;
if (enable && addEntryView != null && addEntryView.getVisibility() != VISIBLE)
addEntryView.setVisibility(INVISIBLE);
+ disableViewIfNoAddAvailable();
}
/**
@@ -177,6 +178,19 @@ public class AddNodeButtonView extends RelativeLayout {
this.addGroupEnable = enable;
if (enable && addGroupView != null && addGroupView.getVisibility() != VISIBLE)
addGroupView.setVisibility(INVISIBLE);
+ disableViewIfNoAddAvailable();
+ }
+
+ private void disableViewIfNoAddAvailable() {
+ if (!addEntryEnable || !addGroupEnable) {
+ setVisibility(GONE);
+ } else {
+ setVisibility(VISIBLE);
+ }
+ }
+
+ public boolean isVisible() {
+ return getVisibility() == VISIBLE;
}
public void setAddGroupClickListener(OnClickListener onClickListener) {
diff --git a/app/src/main/res/drawable/ic_read_only_white_24dp.xml b/app/src/main/res/drawable/ic_read_only_white_24dp.xml
new file mode 100644
index 000000000..7f6522b98
--- /dev/null
+++ b/app/src/main/res/drawable/ic_read_only_white_24dp.xml
@@ -0,0 +1,19 @@
+
+