Merge branch 'feature/Search' into develop #26 #147 #153

This commit is contained in:
J-Jamet
2018-07-27 22:19:43 +02:00
47 changed files with 1082 additions and 766 deletions

View File

@@ -29,7 +29,7 @@ import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.database.action.node.DeleteGroupRunnable;
import com.kunzisoft.keepass.search.SearchDbHelper;
import com.kunzisoft.keepass.database.search.SearchDbHelper;
import java.util.List;
@@ -72,8 +72,8 @@ public class DeleteEntry extends AndroidTestCase {
// Verify the entries were removed from the search index
SearchDbHelper dbHelp = new SearchDbHelper(ctx);
PwGroup results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME);
PwGroup results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME);
PwGroup results1 = dbHelp.search(db.getPwDatabase(), ENTRY1_NAME, 100);
PwGroup results2 = dbHelp.search(db.getPwDatabase(), ENTRY2_NAME, 100);
assertEquals("Entry1 was not removed from the search results", 0, results1.numbersOfChildEntries());
assertEquals("Entry2 was not removed from the search results", 0, results2.numbersOfChildEntries());

View File

@@ -19,11 +19,11 @@
*/
package com.kunzisoft.keepass.tests.database;
import junit.framework.TestCase;
import com.kunzisoft.keepass.database.PwDatabaseV4;
import com.kunzisoft.keepass.database.PwEntryV4;
import junit.framework.TestCase;
public class EntryV4 extends TestCase {
public void testBackup() {
@@ -46,7 +46,7 @@ public class EntryV4 extends TestCase {
entry.createBackup(db);
PwEntryV4 backup = entry.getHistory().get(0);
entry.endToManageFieldReferences();
entry.stopToManageFieldReferences();
assertEquals("Title2", backup.getTitle());
assertEquals("User2", backup.getUsername());
}

View File

@@ -104,11 +104,19 @@
<activity
android:name="com.kunzisoft.keepass.activities.GroupActivity"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="adjustPan">
android:windowSoftInputMode="adjustPan"
android:launchMode="singleTop">
<meta-data
android:name="android.app.default_searchable"
android:value="com.kunzisoft.keepass.search.SearchResults"
android:exported="false"/>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<activity
android:name="com.kunzisoft.keepass.activities.EntryActivity"
@@ -118,17 +126,6 @@
android:name="com.kunzisoft.keepass.activities.EntryEditActivity"
android:configChanges="orientation|keyboardHidden"
android:windowSoftInputMode="stateHidden" />
<activity
android:name="com.kunzisoft.keepass.search.SearchResultsActivity"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<activity android:name="com.kunzisoft.keepass.settings.SettingsActivity" />
<activity android:name="com.kunzisoft.keepass.autofill.AutoFillAuthActivity"
android:configChanges="orientation|keyboardHidden" />

View File

@@ -225,7 +225,7 @@ public class EntryActivity extends LockingHideActivity {
startService(intent);
}
mEntry.endToManageFieldReferences();
mEntry.stopToManageFieldReferences();
}
firstLaunchOfActivity = false;
}
@@ -369,7 +369,7 @@ public class EntryActivity extends LockingHideActivity {
entryContentsView.assignExpiresDate(getString(R.string.never));
}
mEntry.endToManageFieldReferences();
mEntry.stopToManageFieldReferences();
}
@Override

View File

@@ -429,7 +429,7 @@ public class EntryEditActivity extends LockingHideActivity
}
}
newEntry.endToManageFieldReferences();
newEntry.stopToManageFieldReferences();
return newEntry;
}
@@ -484,7 +484,7 @@ public class EntryEditActivity extends LockingHideActivity
}
// Don't start the field reference manager, we want to see the raw ref
mEntry.endToManageFieldReferences();
mEntry.stopToManageFieldReferences();
entryTitleView.setText(mEntry.getTitle());
entryUserNameView.setText(mEntry.getUsername());

View File

@@ -19,6 +19,7 @@
*/
package com.kunzisoft.keepass.activities;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.app.SearchManager;
@@ -29,24 +30,31 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.adapters.NodeAdapter;
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.autofill.AutofillHelper;
import com.kunzisoft.keepass.database.Database;
@@ -57,6 +65,7 @@ import com.kunzisoft.keepass.database.PwGroupId;
import com.kunzisoft.keepass.database.PwIcon;
import com.kunzisoft.keepass.database.PwIconStandard;
import com.kunzisoft.keepass.database.PwNode;
import com.kunzisoft.keepass.database.SortNodeEnum;
import com.kunzisoft.keepass.database.action.node.AddGroupRunnable;
import com.kunzisoft.keepass.database.action.node.AfterActionNodeOnFinish;
import com.kunzisoft.keepass.database.action.node.CopyEntryRunnable;
@@ -69,48 +78,64 @@ import com.kunzisoft.keepass.dialogs.AssignMasterKeyDialogFragment;
import com.kunzisoft.keepass.dialogs.GroupEditDialogFragment;
import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment;
import com.kunzisoft.keepass.dialogs.ReadOnlyDialog;
import com.kunzisoft.keepass.dialogs.SortDialogFragment;
import com.kunzisoft.keepass.icons.IconPackChooser;
import com.kunzisoft.keepass.search.SearchResultsActivity;
import com.kunzisoft.keepass.lock.LockingActivity;
import com.kunzisoft.keepass.password.AssignPasswordHelper;
import com.kunzisoft.keepass.selection.EntrySelectionHelper;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.tasks.SaveDatabaseProgressTaskDialogFragment;
import com.kunzisoft.keepass.tasks.UIToastTask;
import com.kunzisoft.keepass.tasks.UpdateProgressTaskStatus;
import com.kunzisoft.keepass.utils.MenuUtil;
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
public class GroupActivity extends LockingActivity
implements GroupEditDialogFragment.EditGroupListener,
IconPickerDialogFragment.IconPickerListener,
NodeAdapter.NodeMenuListener,
ListNodesFragment.OnScrollListener {
ListNodesFragment.OnScrollListener,
AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
NodeAdapter.NodeClickCallback,
SortDialogFragment.SortSelectionListener {
private static final String TAG = GroupActivity.class.getName();
protected static final String GROUP_ID_KEY = "GROUP_ID_KEY";
private static final String GROUP_ID_KEY = "GROUP_ID_KEY";
private static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
private static final String SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG";
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";
private static final String NODE_TO_MOVE_KEY = "NODE_TO_MOVE_KEY";
private Toolbar toolbar;
private View searchTitleView;
private ExpandableLayout toolbarPasteExpandableLayout;
private Toolbar toolbarPaste;
private ImageView iconView;
private AddNodeButtonView addNodeButtonView;
private TextView groupNameView;
protected boolean addGroupEnabled = false;
protected boolean addEntryEnabled = false;
protected boolean isRoot = false;
private Database database;
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";
private static final String NODE_TO_MOVE_KEY = "NODE_TO_MOVE_KEY";
private ListNodesFragment listNodesFragment;
private boolean currentGroupIsASearch;
private PwGroup rootGroup;
private PwGroup mCurrentGroup;
private PwGroup oldGroupToUpdate;
private PwNode nodeToCopy;
private PwNode nodeToMove;
private boolean entrySelectionMode;
private AutofillHelper autofillHelper;
private SearchEntryCursorAdapter searchSuggestionAdapter;
// After a database creation
public static void launch(Activity act) {
launch(act, READ_ONLY_DEFAULT);
@@ -176,39 +201,37 @@ public class GroupActivity extends ListNodesActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
Log.i(TAG, "Started creating tree");
if ( mCurrentGroup == null ) {
Log.w(TAG, "Group was null");
return;
}
if ( isFinishing() ) {
return;
}
database = App.getDB();
// Likely the app has been killed exit the activity
if ( ! database.getLoaded() ) {
finish();
return;
}
// Construct main view
setContentView(getLayoutInflater().inflate(R.layout.list_nodes_with_add_button, null));
attachFragmentToContentView();
// Initialize views
iconView = findViewById(R.id.icon);
addNodeButtonView = findViewById(R.id.add_node_button);
addNodeButtonView.enableAddGroup(addGroupEnabled);
addNodeButtonView.enableAddEntry(addEntryEnabled);
toolbar = findViewById(R.id.toolbar);
toolbar.setTitle("");
setSupportActionBar(toolbar);
searchTitleView = findViewById(R.id.search_title);
groupNameView = findViewById(R.id.group_name);
toolbarPasteExpandableLayout = findViewById(R.id.expandable_toolbar_paste_layout);
toolbarPaste = findViewById(R.id.toolbar_paste);
toolbarPaste.inflateMenu(R.menu.node_paste_menu);
toolbarPaste.setNavigationIcon(R.drawable.ic_arrow_left_white_24dp);
toolbarPaste.setNavigationOnClickListener(view -> {
toolbarPasteExpandableLayout.collapse();
nodeToCopy = null;
nodeToMove = null;
});
invalidateOptionsMenu();
// Get arg from intent or instance state
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, getIntent());
// Retrieve elements after an orientation change
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(OLD_GROUP_TO_UPDATE_KEY))
oldGroupToUpdate = savedInstanceState.getParcelable(OLD_GROUP_TO_UPDATE_KEY);
@@ -223,21 +246,124 @@ public class GroupActivity extends ListNodesActivity
}
}
addNodeButtonView.setAddGroupClickListener(v -> {
GroupEditDialogFragment.build()
.show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP);
rootGroup = database.getPwDatabase().getRootGroup();
mCurrentGroup = retrieveCurrentGroup(getIntent(), savedInstanceState);
currentGroupIsASearch = Intent.ACTION_SEARCH.equals(getIntent().getAction());
Log.i(TAG, "Started creating tree");
if ( mCurrentGroup == null ) {
Log.w(TAG, "Group was null");
return;
}
toolbar.setTitle("");
setSupportActionBar(toolbar);
toolbarPaste.inflateMenu(R.menu.node_paste_menu);
toolbarPaste.setNavigationIcon(R.drawable.ic_arrow_left_white_24dp);
toolbarPaste.setNavigationOnClickListener(view -> {
toolbarPasteExpandableLayout.collapse();
nodeToCopy = null;
nodeToMove = null;
});
String fragmentTag = LIST_NODES_FRAGMENT_TAG;
if (currentGroupIsASearch)
fragmentTag = SEARCH_FRAGMENT_TAG;
// Initialize the fragment with the list
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
.findFragmentByTag(fragmentTag);
if (listNodesFragment == null)
listNodesFragment = ListNodesFragment.newInstance(mCurrentGroup, readOnly, currentGroupIsASearch);
// Attach fragment to content view
getSupportFragmentManager().beginTransaction().replace(
R.id.nodes_list_fragment_container,
listNodesFragment,
fragmentTag)
.commit();
// Add listeners to the add buttons
addNodeButtonView.setAddGroupClickListener(v -> GroupEditDialogFragment.build()
.show(getSupportFragmentManager(),
GroupEditDialogFragment.TAG_CREATE_GROUP));
addNodeButtonView.setAddEntryClickListener(v ->
EntryEditActivity.launch(GroupActivity.this, mCurrentGroup));
Log.i(TAG, "Finished creating tree");
if (isRoot) {
showWarnings();
// To init autofill
entrySelectionMode = EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
autofillHelper = new AutofillHelper();
autofillHelper.retrieveAssistStructure(getIntent());
}
// Search suggestion
searchSuggestionAdapter = new SearchEntryCursorAdapter(this, database);
Log.i(TAG, "Finished creating tree");
}
@Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
// only one instance of search in backstack
openSearchGroup(retrieveCurrentGroup(intent, null));
currentGroupIsASearch = true;
} else {
currentGroupIsASearch = false;
}
}
private void openSearchGroup(PwGroup group) {
// Delete the previous search fragment
Fragment searchFragment = getSupportFragmentManager().findFragmentByTag(SEARCH_FRAGMENT_TAG);
if (searchFragment != null) {
if ( getSupportFragmentManager()
.popBackStackImmediate(SEARCH_FRAGMENT_TAG, FragmentManager.POP_BACK_STACK_INCLUSIVE) )
getSupportFragmentManager().beginTransaction().remove(searchFragment).commit();
}
openGroup(group, true);
}
private void openChildGroup(PwGroup group) {
openGroup(group, false);
}
private void openGroup(PwGroup group, boolean isASearch) {
// Check Timeout
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
// Open a group in a new fragment
ListNodesFragment newListNodeFragment = ListNodesFragment.newInstance(group, readOnly, isASearch);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
// Different animation
String fragmentTag;
if (isASearch) {
fragmentTransaction.setCustomAnimations(R.anim.slide_in_top, R.anim.slide_out_bottom,
R.anim.slide_in_bottom, R.anim.slide_out_top);
fragmentTag = SEARCH_FRAGMENT_TAG;
} else {
fragmentTransaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left,
R.anim.slide_in_left, R.anim.slide_out_right);
fragmentTag = LIST_NODES_FRAGMENT_TAG;
}
fragmentTransaction.replace(R.id.nodes_list_fragment_container,
newListNodeFragment,
fragmentTag);
fragmentTransaction.addToBackStack(fragmentTag);
fragmentTransaction.commit();
listNodesFragment = newListNodeFragment;
mCurrentGroup = group;
assignGroupViewElements();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
outState.putParcelable(GROUP_ID_KEY, mCurrentGroup.getId());
@@ -246,65 +372,113 @@ public class GroupActivity extends ListNodesActivity
outState.putParcelable(NODE_TO_COPY_KEY, nodeToCopy);
if (nodeToMove != null)
outState.putParcelable(NODE_TO_MOVE_KEY, nodeToMove);
ReadOnlyHelper.onSaveInstanceState(outState, readOnly);
super.onSaveInstanceState(outState);
}
protected PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState) {
protected PwGroup retrieveCurrentGroup(Intent intent, @Nullable Bundle savedInstanceState) {
PwGroupId pwGroupId = null;
if (savedInstanceState != null
&& savedInstanceState.containsKey(GROUP_ID_KEY)) {
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY);
} else {
if (getIntent() != null)
pwGroupId = getIntent().getParcelableExtra(GROUP_ID_KEY);
// If it's a search
if ( Intent.ACTION_SEARCH.equals(intent.getAction()) ) {
return database.search(intent.getStringExtra(SearchManager.QUERY).trim());
}
// else a real group
else {
PwGroupId pwGroupId = null;
if (savedInstanceState != null
&& savedInstanceState.containsKey(GROUP_ID_KEY)) {
pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY);
} else {
if (getIntent() != null)
pwGroupId = intent.getParcelableExtra(GROUP_ID_KEY);
}
readOnly = database.isReadOnly() || readOnly; // Force read only if the database is like that
readOnly = database.isReadOnly() || readOnly; // Force read only if the database is like that
Log.w(TAG, "Creating tree view");
PwGroup currentGroup;
if ( pwGroupId == null ) {
currentGroup = rootGroup;
} else {
currentGroup = database.getPwDatabase().getGroupByGroupId(pwGroupId);
Log.w(TAG, "Creating tree view");
PwGroup currentGroup;
if (pwGroupId == null) {
currentGroup = rootGroup;
} else {
currentGroup = database.getPwDatabase().getGroupByGroupId(pwGroupId);
}
return currentGroup;
}
if (currentGroup != null) {
addGroupEnabled = !readOnly;
addEntryEnabled = !readOnly;
isRoot = (currentGroup == rootGroup);
if (!currentGroup.allowAddEntryIfIsRoot())
addEntryEnabled = !isRoot && addEntryEnabled;
}
return currentGroup;
}
@Override
public void assignToolbarElements() {
super.assignToolbarElements();
// Assign the group icon depending of IconPack or custom icon
if ( mCurrentGroup != null ) {
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
// Retrieve the textColor to tint the icon
int[] attrs = {R.attr.textColorInverse};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
int iconColor = ta.getColor(0, Color.WHITE);
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon(), true, iconColor);
public void assignGroupViewElements() {
// Assign title
if (mCurrentGroup != null) {
String title = mCurrentGroup.getName();
if (title != null && title.length() > 0) {
if (groupNameView != null) {
groupNameView.setText(title);
groupNameView.invalidate();
}
} else {
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon());
}
if (toolbar != null) {
if ( mCurrentGroup.containsParent() )
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
else {
toolbar.setNavigationIcon(null);
if (groupNameView != null) {
groupNameView.setText(getText(R.string.root));
groupNameView.invalidate();
}
}
}
if (currentGroupIsASearch) {
searchTitleView.setVisibility(View.VISIBLE);
} else {
searchTitleView.setVisibility(View.GONE);
}
// Assign icon
if (currentGroupIsASearch) {
if (toolbar != null) {
toolbar.setNavigationIcon(null);
}
iconView.setVisibility(View.GONE);
} else {
// Assign the group icon depending of IconPack or custom icon
iconView.setVisibility(View.VISIBLE);
if (mCurrentGroup != null) {
if (IconPackChooser.getSelectedIconPack(this).tintable()) {
// Retrieve the textColor to tint the icon
int[] attrs = {R.attr.textColorInverse};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
int iconColor = ta.getColor(0, Color.WHITE);
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon(), true, iconColor);
} else {
App.getDB().getDrawFactory().assignDatabaseIconTo(this, iconView, mCurrentGroup.getIcon());
}
if (toolbar != null) {
if (mCurrentGroup.containsParent())
toolbar.setNavigationIcon(R.drawable.ic_arrow_up_white_24dp);
else {
toolbar.setNavigationIcon(null);
}
}
}
}
// Show button if allowed
if (addNodeButtonView != null) {
// To enable add button
boolean addGroupEnabled = !readOnly && !currentGroupIsASearch;
boolean addEntryEnabled = !readOnly && !currentGroupIsASearch;
if (mCurrentGroup != null) {
boolean isRoot = (mCurrentGroup == rootGroup);
if (!mCurrentGroup.allowAddEntryIfIsRoot())
addEntryEnabled = !isRoot && addEntryEnabled;
if (isRoot) {
showWarnings();
}
}
addNodeButtonView.enableAddGroup(addGroupEnabled);
addNodeButtonView.enableAddEntry(addEntryEnabled);
if (addNodeButtonView.isEnable())
addNodeButtonView.showButton();
}
}
@Override
@@ -313,6 +487,50 @@ public class GroupActivity extends ListNodesActivity
addNodeButtonView.hideButtonOnScrollListener(dy);
}
@Override
public void onNodeClick(PwNode node) {
// Add event when we have Autofill
AssistStructure assistStructure = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
assistStructure = autofillHelper.getAssistStructure();
if (assistStructure != null) {
switch (node.getType()) {
case GROUP:
openChildGroup((PwGroup) node);
break;
case ENTRY:
// Build response with the entry selected
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
}
}
if ( assistStructure == null ){
if (entrySelectionMode) {
switch (node.getType()) {
case GROUP:
openChildGroup((PwGroup) node);
break;
case ENTRY:
EntrySelectionHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
} else {
switch (node.getType()) {
case GROUP:
openChildGroup((PwGroup) node);
break;
case ENTRY:
EntryActivity.launch(this, (PwEntry) node, readOnly);
break;
}
}
}
}
@Override
public boolean onOpenMenuClick(PwNode node) {
onNodeClick(node);
@@ -485,9 +703,8 @@ public class GroupActivity extends ListNodesActivity
@Override
protected void onResume() {
super.onResume();
// Show button on resume
if (addNodeButtonView != null)
addNodeButtonView.showButton();
// Refresh the elements
assignGroupViewElements();
}
/**
@@ -501,7 +718,7 @@ public class GroupActivity extends ListNodesActivity
if (listNodesFragment != null
&& listNodesFragment.isEmpty()) {
if (!PreferencesUtil.isEducationNewNodePerformed(this)
&& addNodeButtonView.isVisible()) {
&& addNodeButtonView.isEnable()) {
TapTargetView.showFor(this,
TapTarget.forView(findViewById(R.id.add_button),
@@ -652,11 +869,26 @@ public class GroupActivity extends ListNodesActivity
searchView = (SearchView) searchItem.getActionView();
}
if (searchView != null) {
// TODO Flickering when locking, will be better with content provider
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, SearchResultsActivity.class)));
searchView.setSearchableInfo(searchManager.getSearchableInfo(new ComponentName(this, GroupActivity.class)));
searchView.setIconifiedByDefault(false); // Do not iconify the widget; expand it by default
searchView.setSuggestionsAdapter(searchSuggestionAdapter);
searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
@Override
public boolean onSuggestionClick(int position) {
onNodeClick(searchSuggestionAdapter.getEntryFromPosition(position));
return true;
}
@Override
public boolean onSuggestionSelect(int position) {
return true;
}
});
}
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.default_menu, menu);
super.onCreateOptionsMenu(menu);
// Launch education screen
@@ -673,7 +905,7 @@ public class GroupActivity extends ListNodesActivity
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
// manually launch the real search activity
final Intent searchIntent = new Intent(getApplicationContext(), SearchResultsActivity.class);
final Intent searchIntent = new Intent(getApplicationContext(), GroupActivity.class);
// add query to the Intent Extras
searchIntent.setAction(Intent.ACTION_SEARCH);
searchIntent.putExtra(SearchManager.QUERY, query);
@@ -706,7 +938,7 @@ public class GroupActivity extends ListNodesActivity
return true;
case R.id.menu_search:
onSearchRequested();
//onSearchRequested();
return true;
case R.id.menu_lock:
@@ -716,8 +948,11 @@ public class GroupActivity extends ListNodesActivity
case R.id.menu_change_master_key:
setPassword();
return true;
default:
// Check the time lock before launching settings
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, readOnly, true);
return super.onOptionsItemSelected(item);
}
return super.onOptionsItemSelected(item);
}
private void setPassword() {
@@ -789,6 +1024,7 @@ public class GroupActivity extends ListNodesActivity
class AfterAddNode extends AfterActionNodeOnFinish {
@Override
public void run(PwNode oldNode, PwNode newNode) {
super.run();
@@ -807,6 +1043,7 @@ public class GroupActivity extends ListNodesActivity
class AfterUpdateNode extends AfterActionNodeOnFinish {
@Override
public void run(PwNode oldNode, PwNode newNode) {
super.run();
@@ -892,16 +1129,79 @@ public class GroupActivity extends ListNodesActivity
}
@Override
protected void openGroup(PwGroup group) {
super.openGroup(group);
if (addNodeButtonView != null)
addNodeButtonView.showButton();
public void onAssignKeyDialogPositiveClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
AssignPasswordHelper assignPasswordHelper =
new AssignPasswordHelper(this,
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
assignPasswordHelper.assignPasswordInDatabase(null);
}
@Override
public void onAssignKeyDialogNegativeClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
if (listNodesFragment != null)
listNodesFragment.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
EntrySelectionHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
}
}
@SuppressLint("RestrictedApi")
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
/*
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
*/
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
int flags = intent.getFlags();
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
intent.setFlags(flags);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
super.startActivityForResult(intent, requestCode, options);
}
}
private void removeSearchInIntent(Intent intent) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
currentGroupIsASearch = false;
intent.setAction(Intent.ACTION_DEFAULT);
intent.removeExtra(SearchManager.QUERY);
}
}
@Override
public void onBackPressed() {
super.onBackPressed();
if (addNodeButtonView != null)
addNodeButtonView.showButton();
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
super.onBackPressed();
listNodesFragment = (ListNodesFragment) getSupportFragmentManager().findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
// to refresh fragment
listNodesFragment.rebuildList();
mCurrentGroup = listNodesFragment.getMainGroup();
removeSearchInIntent(getIntent());
assignGroupViewElements();
}
}
}

View File

@@ -1,294 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities;
import android.annotation.SuppressLint;
import android.app.assist.AssistStructure;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.adapters.NodeAdapter;
import com.kunzisoft.keepass.app.App;
import com.kunzisoft.keepass.autofill.AutofillHelper;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwGroup;
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.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
implements AssignMasterKeyDialogFragment.AssignPasswordDialogListener,
NodeAdapter.NodeClickCallback,
SortDialogFragment.SortSelectionListener {
protected static final String LIST_NODES_FRAGMENT_TAG = "LIST_NODES_FRAGMENT_TAG";
protected ListNodesFragment listNodesFragment;
protected Database database;
protected PwGroup rootGroup;
protected PwGroup mCurrentGroup;
protected TextView groupNameView;
protected boolean entrySelectionMode;
protected AutofillHelper autofillHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ( isFinishing() ) {
return;
}
database = App.getDB();
// Likely the app has been killed exit the activity
if ( ! database.getLoaded() ) {
finish();
return;
}
invalidateOptionsMenu();
rootGroup = database.getPwDatabase().getRootGroup();
mCurrentGroup = retrieveCurrentGroup(savedInstanceState);
initializeListNodesFragment(mCurrentGroup);
entrySelectionMode = EntrySelectionHelper.isIntentInEntrySelectionMode(getIntent());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
autofillHelper = new AutofillHelper();
autofillHelper.retrieveAssistStructure(getIntent());
}
}
protected abstract PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState);
@Override
protected void onResume() {
super.onResume();
// Refresh the title
assignToolbarElements();
}
protected void initializeListNodesFragment(PwGroup currentGroup) {
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
.findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
if (listNodesFragment == null)
listNodesFragment = ListNodesFragment.newInstance(currentGroup, readOnly);
}
/**
* Attach the fragment's list of node.
* <br />
* <strong>R.id.nodes_list_fragment_container</strong> must be the id of the container
*/
protected void attachFragmentToContentView() {
getSupportFragmentManager().beginTransaction().replace(
R.id.nodes_list_fragment_container,
listNodesFragment,
LIST_NODES_FRAGMENT_TAG)
.commit();
}
public void assignToolbarElements() {
if (mCurrentGroup != null) {
String title = mCurrentGroup.getName();
if (title != null && title.length() > 0) {
if (groupNameView != null) {
groupNameView.setText(title);
groupNameView.invalidate();
}
} else {
if (groupNameView != null) {
groupNameView.setText(getText(R.string.root));
groupNameView.invalidate();
}
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.default_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
default:
// Check the time lock before launching settings
MenuUtil.onDefaultMenuOptionsItemSelected(this, item, readOnly, true);
return super.onOptionsItemSelected(item);
}
}
@Override
public void onNodeClick(PwNode node) {
// Add event when we have Autofill
AssistStructure assistStructure = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
assistStructure = autofillHelper.getAssistStructure();
if (assistStructure != null) {
switch (node.getType()) {
case GROUP:
openGroup((PwGroup) node);
break;
case ENTRY:
// Build response with the entry selected
autofillHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
}
}
if ( assistStructure == null ){
if (entrySelectionMode) {
switch (node.getType()) {
case GROUP:
openGroup((PwGroup) node);
break;
case ENTRY:
EntrySelectionHelper.buildResponseWhenEntrySelected(this, (PwEntry) node);
finish();
break;
}
} else {
switch (node.getType()) {
case GROUP:
openGroup((PwGroup) node);
break;
case ENTRY:
EntryActivity.launch(this, (PwEntry) node, readOnly);
break;
}
}
}
}
protected void openGroup(PwGroup group) {
// Check Timeout
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
ListNodesFragment newListNodeFragment = ListNodesFragment.newInstance(group, readOnly);
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.nodes_list_fragment_container,
newListNodeFragment,
LIST_NODES_FRAGMENT_TAG)
.addToBackStack(LIST_NODES_FRAGMENT_TAG)
.commit();
listNodesFragment = newListNodeFragment;
mCurrentGroup = group;
assignToolbarElements();
}
}
@Override
public void onAssignKeyDialogPositiveClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
AssignPasswordHelper assignPasswordHelper =
new AssignPasswordHelper(this,
masterPasswordChecked, masterPassword, keyFileChecked, keyFile);
assignPasswordHelper.assignPasswordInDatabase(null);
}
@Override
public void onAssignKeyDialogNegativeClick(
boolean masterPasswordChecked, String masterPassword,
boolean keyFileChecked, Uri keyFile) {
}
@Override
public void onSortSelected(SortNodeEnum sortNodeEnum, boolean ascending, boolean groupsBefore, boolean recycleBinBottom) {
if (listNodesFragment != null)
listNodesFragment.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
EntrySelectionHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
AutofillHelper.onActivityResultSetResultAndFinish(this, requestCode, resultCode, data);
}
}
@SuppressLint("RestrictedApi")
@Override
public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
/*
* ACTION_SEARCH automatically forces a new task. This occurs when you open a kdb file in
* another app such as Files or GoogleDrive and then Search for an entry. Here we remove the
* FLAG_ACTIVITY_NEW_TASK flag bit allowing search to open it's activity in the current task.
*/
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
int flags = intent.getFlags();
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
intent.setFlags(flags);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
super.startActivityForResult(intent, requestCode, options);
}
}
@Override
public void onBackPressed() {
if (checkTimeIsAllowedOrFinish(this)) {
startRecordTime(this);
super.onBackPressed();
listNodesFragment = (ListNodesFragment) getSupportFragmentManager().findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
// to refresh fragment
listNodesFragment.rebuildList();
mCurrentGroup = listNodesFragment.getMainGroup();
assignToolbarElements();
}
}
}

View File

@@ -34,25 +34,30 @@ public class ListNodesFragment extends StylishFragment implements
private static final String TAG = ListNodesFragment.class.getName();
private static final String GROUP_KEY = "GROUP_KEY";
private static final String IS_SEARCH = "IS_SEARCH";
private NodeAdapter.NodeClickCallback nodeClickCallback;
private NodeAdapter.NodeMenuListener nodeMenuListener;
private OnScrollListener onScrollListener;
private RecyclerView listView;
protected PwGroup mCurrentGroup;
protected NodeAdapter mAdapter;
private PwGroup currentGroup;
private NodeAdapter mAdapter;
private View notFoundView;
private boolean isASearchResult;
// Preferences for sorting
private SharedPreferences prefs;
private boolean readOnly;
public static ListNodesFragment newInstance(PwGroup group, boolean readOnly) {
public static ListNodesFragment newInstance(PwGroup group, boolean readOnly, boolean isASearch) {
Bundle bundle = new Bundle();
if (group != null) {
bundle.putParcelable(GROUP_KEY, group);
}
bundle.putBoolean(IS_SEARCH, isASearch);
ReadOnlyHelper.putReadOnlyInBundle(bundle, readOnly);
ListNodesFragment listNodesFragment = new ListNodesFragment();
listNodesFragment.setArguments(bundle);
@@ -99,11 +104,17 @@ public class ListNodesFragment extends StylishFragment implements
if (getArguments() != null) {
// Contains all the group in element
if (getArguments().containsKey(GROUP_KEY)) {
mCurrentGroup = getArguments().getParcelable(GROUP_KEY);
currentGroup = getArguments().getParcelable(GROUP_KEY);
}
if (getArguments().containsKey(IS_SEARCH)) {
isASearchResult = getArguments().getBoolean(IS_SEARCH);
}
}
mAdapter = new NodeAdapter(getContextThemed(), getActivity().getMenuInflater(), readOnly);
mAdapter = new NodeAdapter(getContextThemed(), getActivity().getMenuInflater());
mAdapter.setReadOnly(readOnly);
mAdapter.setIsASearchResult(isASearchResult);
mAdapter.setOnNodeClickListener(nodeClickCallback);
if (nodeMenuListener != null) {
@@ -129,6 +140,7 @@ public class ListNodesFragment extends StylishFragment implements
View rootView = inflater.cloneInContext(getContextThemed())
.inflate(R.layout.list_nodes_fragment, container, false);
listView = rootView.findViewById(R.id.nodes_list);
notFoundView = rootView.findViewById(R.id.not_found_container);
if (onScrollListener != null) {
listView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@@ -148,11 +160,20 @@ public class ListNodesFragment extends StylishFragment implements
super.onResume();
rebuildList();
if (isASearchResult && mAdapter.isEmpty()) {
// To show the " no search entry found "
listView.setVisibility(View.GONE);
notFoundView.setVisibility(View.VISIBLE);
} else {
listView.setVisibility(View.VISIBLE);
notFoundView.setVisibility(View.GONE);
}
}
public void rebuildList() {
// Add elements to the list
mAdapter.rebuildList(mCurrentGroup);
mAdapter.rebuildList(currentGroup);
assignListToNodeAdapter(listView);
}
@@ -174,7 +195,7 @@ public class ListNodesFragment extends StylishFragment implements
// Tell the adapter to refresh it's list
mAdapter.notifyChangeSort(sortNodeEnum, ascending, groupsBefore);
mAdapter.rebuildList(mCurrentGroup);
mAdapter.rebuildList(currentGroup);
}
@Override
@@ -232,7 +253,7 @@ public class ListNodesFragment extends StylishFragment implements
mAdapter.addNode(newNode);
if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) {
//mAdapter.updateLastNodeRegister(newNode);
mAdapter.rebuildList(mCurrentGroup);
mAdapter.rebuildList(currentGroup);
}
} else {
Log.e(this.getClass().getName(), "New node can be retrieve in Activity Result");
@@ -259,7 +280,7 @@ public class ListNodesFragment extends StylishFragment implements
}
public PwGroup getMainGroup() {
return mCurrentGroup;
return currentGroup;
}
public interface OnScrollListener {

View File

@@ -62,6 +62,7 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
private NodeMenuListener nodeMenuListener;
private boolean activateContextMenu;
private boolean readOnly;
private boolean isASearchResult;
private int iconGroupColor;
private int iconEntryColor;
@@ -70,13 +71,14 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
* Create node list adapter with contextMenu or not
* @param context Context to use
*/
public NodeAdapter(final Context context, MenuInflater menuInflater, boolean readOnly) {
public NodeAdapter(final Context context, MenuInflater menuInflater) {
this.inflater = LayoutInflater.from(context);
this.menuInflater = menuInflater;
this.context = context;
assignPreferences();
this.activateContextMenu = false;
this.readOnly = readOnly;
this.readOnly = false;
this.isASearchResult = false;
this.nodeSortedList = new SortedList<>(PwNode.class, new SortedListAdapterCallback<PwNode>(this) {
@Override public int compare(PwNode item1, PwNode item2) {
@@ -103,6 +105,14 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
taTextColor.recycle();
}
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
public void setIsASearchResult(boolean isASearchResult) {
this.isASearchResult = isASearchResult;
}
public void setActivateContextMenu(boolean activate) {
this.activateContextMenu = activate;
}
@@ -136,6 +146,14 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
}
}
/**
* Determine if the adapter contains or not any element
* @return true if the list is empty
*/
public boolean isEmpty() {
return nodeSortedList.size() <= 0;
}
/**
* Add a node to the list
* @param node Node to add
@@ -301,29 +319,44 @@ public class NodeAdapter extends RecyclerView.Adapter<BasicViewHolder> {
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
menuInflater.inflate(R.menu.node_menu, contextMenu);
// Opening
MenuItem menuItem = contextMenu.findItem(R.id.menu_open);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
// Edition
if (readOnly || node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
contextMenu.removeItem(R.id.menu_edit);
contextMenu.removeItem(R.id.menu_copy);
contextMenu.removeItem(R.id.menu_move);
contextMenu.removeItem(R.id.menu_delete);
} else {
// Edition
menuItem = contextMenu.findItem(R.id.menu_edit);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
// Copy (not for group)
if (node.getType().equals(PwNode.Type.ENTRY)) {
menuItem = contextMenu.findItem(R.id.menu_copy);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
} else {
// TODO COPY For Group
contextMenu.removeItem(R.id.menu_copy);
}
// Move
}
// Copy (not for group)
if (readOnly
|| isASearchResult
|| node.equals(App.getDB().getPwDatabase().getRecycleBin())
|| node.getType().equals(PwNode.Type.GROUP)) {
// TODO COPY For Group
contextMenu.removeItem(R.id.menu_copy);
} else {
menuItem = contextMenu.findItem(R.id.menu_copy);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
}
// Move
if (readOnly
|| isASearchResult
|| node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
contextMenu.removeItem(R.id.menu_move);
} else {
menuItem = contextMenu.findItem(R.id.menu_move);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
// Deletion
}
// Deletion
if (readOnly || node.equals(App.getDB().getPwDatabase().getRecycleBin())) {
contextMenu.removeItem(R.id.menu_delete);
} else {
menuItem = contextMenu.findItem(R.id.menu_delete);
menuItem.setOnMenuItemClickListener(mOnMyActionClickListener);
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2018 Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Color;
import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.Database;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwIcon;
import com.kunzisoft.keepass.database.PwIconFactory;
import com.kunzisoft.keepass.database.PwIconStandard;
import com.kunzisoft.keepass.database.cursor.EntryCursor;
import com.kunzisoft.keepass.icons.IconPackChooser;
import java.util.UUID;
public class SearchEntryCursorAdapter extends CursorAdapter {
private LayoutInflater cursorInflater;
private Database database;
private int iconColor;
public SearchEntryCursorAdapter(Context context, Database database) {
super(context, null, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
cursorInflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
this.database = database;
// Get the icon color
int[] attrTextColor = {R.attr.textColorInverse};
TypedArray taTextColor = context.getTheme().obtainStyledAttributes(attrTextColor);
this.iconColor = taTextColor.getColor(0, Color.WHITE);
taTextColor.recycle();
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = cursorInflater.inflate(R.layout.search_entry, parent ,false);
ViewHolder viewHolder = new ViewHolder();
viewHolder.imageViewIcon = view.findViewById(R.id.entry_icon);
viewHolder.textViewTitle = view.findViewById(R.id.entry_text);
view.setTag(viewHolder);
return view;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
// Retrieve elements from cursor
PwIconFactory iconFactory = database.getPwDatabase().getIconFactory();
PwIcon icon = iconFactory.getIcon(
new UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))));
if (icon.isUnknown()) {
icon = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)));
if (icon.isUnknown())
icon = iconFactory.getKeyIcon();
}
String title = cursor.getString( cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE) );
ViewHolder viewHolder = (ViewHolder) view.getTag();
// Assign image
if (IconPackChooser.getSelectedIconPack(context).tintable()) {
database.getDrawFactory().assignDatabaseIconTo(context, viewHolder.imageViewIcon, icon, true, iconColor);
} else {
database.getDrawFactory().assignDatabaseIconTo(context, viewHolder.imageViewIcon, icon);
}
// Assign title
viewHolder.textViewTitle.setText(title);
}
private static class ViewHolder {
ImageView imageViewIcon;
TextView textViewTitle;
}
@Override
public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
return database.searchEntry(constraint.toString());
}
public PwEntry getEntryFromPosition(int position) {
PwEntry pwEntry = null;
Cursor cursor = this.getCursor();
if (cursor.moveToFirst()
&&
cursor.move(position)) {
pwEntry = database.createEntry();
database.populateEntry(pwEntry, cursor);
}
return pwEntry;
}
}

View File

@@ -21,19 +21,21 @@ package com.kunzisoft.keepass.database;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine;
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory;
import com.kunzisoft.keepass.database.cursor.EntryCursor;
import com.kunzisoft.keepass.database.exception.ContentFileNotFoundException;
import com.kunzisoft.keepass.database.exception.InvalidDBException;
import com.kunzisoft.keepass.database.exception.PwDbOutputException;
import com.kunzisoft.keepass.database.load.Importer;
import com.kunzisoft.keepass.database.load.ImporterFactory;
import com.kunzisoft.keepass.database.save.PwDbOutput;
import com.kunzisoft.keepass.database.search.SearchDbHelper;
import com.kunzisoft.keepass.icons.IconDrawableFactory;
import com.kunzisoft.keepass.search.SearchDbHelper;
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater;
import com.kunzisoft.keepass.utils.UriUtil;
@@ -179,13 +181,17 @@ public class Database {
}
public PwGroup search(String str) {
return search(str, Integer.MAX_VALUE);
}
public PwGroup search(String str, int max) {
if (searchHelper == null) { return null; }
try {
switch (pm.getVersion()) {
case V3:
return ((SearchDbHelper.SearchDbHelperV3) searchHelper).search(((PwDatabaseV3) pm), str);
return ((SearchDbHelper.SearchDbHelperV3) searchHelper).search(((PwDatabaseV3) pm), str, max);
case V4:
return ((SearchDbHelper.SearchDbHelperV4) searchHelper).search(((PwDatabaseV4) pm), str);
return ((SearchDbHelper.SearchDbHelperV4) searchHelper).search(((PwDatabaseV4) pm), str, max);
}
} catch (Exception e) {
Log.e(TAG, "Search can't be performed with this SearchHelper", e);
@@ -193,6 +199,46 @@ public class Database {
return null;
}
public Cursor searchEntry(String query) {
final EntryCursor cursor = new EntryCursor();
// TODO real content provider
if (!query.isEmpty()) {
PwGroup searchResult = search(query, 6);
for (int i=0; i < searchResult.numbersOfChildEntries(); i++) {
PwEntry entry = searchResult.getChildEntryAt(i);
try {
switch (getPwDatabase().getVersion()) {
case V3:
EntryCursor.addEntry(cursor, (PwEntryV3) entry, i);
case V4:
EntryCursor.addEntry(cursor, (PwEntryV4) entry, i);
}
} catch (Exception e) {
Log.e(TAG, "This version of PwGroup can't be populated", e);
}
}
}
return cursor;
}
public void populateEntry(PwEntry pwEntry, Cursor cursor) {
// TODO invert field reference manager
pwEntry.startToManageFieldReferences(getPwDatabase());
PwIconFactory iconFactory = getPwDatabase().getIconFactory();
try {
switch (getPwDatabase().getVersion()) {
case V3:
EntryCursor.populateEntry(cursor, (PwEntryV3) pwEntry, iconFactory);
case V4:
EntryCursor.populateEntry(cursor, (PwEntryV4) pwEntry, iconFactory);
}
} catch (Exception e) {
Log.e(TAG, "This version of PwGroup can't be populated", e);
}
pwEntry.stopToManageFieldReferences();
}
public void saveData(Context ctx) throws IOException, PwDbOutputException {
saveData(ctx, mUri);
}
@@ -464,7 +510,11 @@ public class Database {
}
}
public PwEntry createEntry(PwGroup parent) {
public PwEntry createEntry() {
return createEntry(null);
}
public PwEntry createEntry(@Nullable PwGroup parent) {
PwEntry newPwEntry = null;
try {
switch (getPwDatabase().getVersion()) {

View File

@@ -76,7 +76,7 @@ public abstract class PwEntry<Parent extends PwGroup> extends PwNode<Parent> {
}
public void startToManageFieldReferences(PwDatabase db) {}
public void endToManageFieldReferences() {}
public void stopToManageFieldReferences() {}
public abstract String getTitle();
public abstract void setTitle(String title);

View File

@@ -169,7 +169,7 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
}
@Override
public void endToManageFieldReferences() {
public void stopToManageFieldReferences() {
this.mDatabase = null;
this.mDecodeRef = false;
}
@@ -205,41 +205,31 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
@Override
public void setTitle(String title) {
PwDatabaseV4 db = mDatabase;
boolean protect = db.getMemoryProtection().protectTitle;
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectTitle;
setProtectedString(STR_TITLE, title, protect);
}
@Override
public void setUsername(String user) {
PwDatabaseV4 db = mDatabase;
boolean protect = db.getMemoryProtection().protectUserName;
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectUserName;
setProtectedString(STR_USERNAME, user, protect);
}
@Override
public void setPassword(String pass) {
PwDatabaseV4 db = mDatabase;
boolean protect = db.getMemoryProtection().protectPassword;
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectPassword;
setProtectedString(STR_PASSWORD, pass, protect);
}
@Override
public void setUrl(String url) {
PwDatabaseV4 db = mDatabase;
boolean protect = db.getMemoryProtection().protectUrl;
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectUrl;
setProtectedString(STR_URL, url, protect);
}
@Override
public void setNotes(String notes) {
PwDatabaseV4 db = mDatabase;
boolean protect = db.getMemoryProtection().protectNotes;
boolean protect = (mDatabase != null) && mDatabase.getMemoryProtection().protectNotes;
setProtectedString(STR_NOTES, notes, protect);
}
@@ -287,7 +277,7 @@ public class PwEntryV4 extends PwEntry<PwGroupV4> implements ITimeLogger {
@Override
public PwIcon getIcon() {
if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
if (customIcon == null || customIcon.getUUID().equals(PwDatabase.UUID_ZERO)) {
return super.getIcon();
} else {
return customIcon;

View File

@@ -225,7 +225,7 @@ public class PwGroupV4 extends PwGroup<PwGroupV4, PwGroupV4, PwEntryV4> implemen
@Override
public PwIcon getIcon() {
if (customIcon == null || customIcon.uuid.equals(PwDatabase.UUID_ZERO)) {
if (customIcon == null || customIcon.getUUID().equals(PwDatabase.UUID_ZERO)) {
return super.getIcon();
} else {
return customIcon;

View File

@@ -32,6 +32,8 @@ public abstract class PwIcon implements Parcelable {
protected PwIcon(Parcel in) {}
public abstract boolean isUnknown();
@Override
public int describeContents() {
return 0;

View File

@@ -26,8 +26,8 @@ import java.util.UUID;
public class PwIconCustom extends PwIcon {
public static final PwIconCustom ZERO = new PwIconCustom(PwDatabase.UUID_ZERO, new byte[0]);
public final UUID uuid;
public byte[] imageData;
private final UUID uuid;
private byte[] imageData;
public PwIconCustom(UUID uuid, byte[] data) {
super();
@@ -48,6 +48,23 @@ public class PwIconCustom extends PwIcon {
}
@Override
public boolean isUnknown() {
return uuid == null || this.equals(ZERO);
}
public UUID getUUID() {
return uuid;
}
public byte[] getImageData() {
return imageData;
}
public void setImageData(byte[] imageData) {
this.imageData = imageData;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(uuid);
dest.writeByteArray(imageData);

View File

@@ -46,8 +46,8 @@ public class PwIconFactory {
}
public PwIconStandard getFolderIcon() {
return getIcon(PwIconStandard.FOLDER);
}
return getIcon(PwIconStandard.FOLDER);
}
public PwIconStandard getIcon(int iconId) {
PwIconStandard icon = (PwIconStandard) cache.get(iconId);
@@ -71,25 +71,8 @@ public class PwIconFactory {
return icon;
}
public PwIconCustom getIcon(UUID iconUuid, byte[] data) {
PwIconCustom icon = (PwIconCustom) customCache.get(iconUuid);
if (icon == null) {
icon = new PwIconCustom(iconUuid, data);
customCache.put(iconUuid, icon);
} else {
icon.imageData = data;
}
return icon;
}
public void setIconData(UUID iconUuid, byte[] data) {
getIcon(iconUuid, data);
}
public void put(PwIconCustom icon) {
customCache.put(icon.uuid, icon);
customCache.put(icon.getUUID(), icon);
}
}

View File

@@ -22,12 +22,17 @@ package com.kunzisoft.keepass.database;
import android.os.Parcel;
public class PwIconStandard extends PwIcon {
public final int iconId;
private final int iconId;
public static final int UNKNOWN = -1;
public static final int KEY = 0;
public static final int TRASH = 43;
public static final int FOLDER = 48;
public PwIconStandard() {
this.iconId = KEY;
}
public PwIconStandard(int iconId) {
this.iconId = iconId;
}
@@ -42,6 +47,15 @@ public class PwIconStandard extends PwIcon {
}
@Override
public boolean isUnknown() {
return iconId == UNKNOWN;
}
public int getIconId() {
return iconId;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(iconId);
}

View File

@@ -33,7 +33,7 @@ import org.joda.time.LocalDate;
public abstract class PwNode<Parent extends PwGroup> implements ISmallTimeLogger, Parcelable, Cloneable {
protected Parent parent = null;
protected PwIconStandard icon = new PwIconStandard(0);
protected PwIconStandard icon = new PwIconStandard();
protected PwDate creation = new PwDate();
protected PwDate lastMod = new PwDate();
protected PwDate lastAccess = new PwDate();

View File

@@ -0,0 +1,81 @@
package com.kunzisoft.keepass.database.cursor;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.provider.BaseColumns;
import android.util.Log;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.PwIconCustom;
import com.kunzisoft.keepass.database.PwIconFactory;
import com.kunzisoft.keepass.database.PwIconStandard;
import java.util.UUID;
public class EntryCursor extends MatrixCursor {
public static final String _ID = BaseColumns._ID;
public static final String COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS = "UUIDMostSignificantBits";
public static final String COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS = "UUIDLeastSignificantBits";
public static final String COLUMN_INDEX_TITLE = "title";
public static final String COLUMN_INDEX_ICON_STANDARD = "iconStandard";
public static final String COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS = "iconCustomUUIDMostSignificantBits";
public static final String COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS = "iconCustomUUIDLeastSignificantBits";
public EntryCursor() {
super(new String[]{ _ID,
COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS,
COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS,
COLUMN_INDEX_TITLE,
COLUMN_INDEX_ICON_STANDARD,
COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS,
COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS});
}
public static void addEntry(MatrixCursor cursor, PwEntryV3 entry, int id) {
cursor.addRow(new Object[] {id,
entry.getUUID().getMostSignificantBits(),
entry.getUUID().getLeastSignificantBits(),
entry.getTitle(),
entry.getIconStandard().getIconId(),
PwDatabase.UUID_ZERO.getMostSignificantBits(),
PwDatabase.UUID_ZERO.getLeastSignificantBits()});
}
public static void addEntry(MatrixCursor cursor, PwEntryV4 entry, int id) {
cursor.addRow(new Object[] {id,
entry.getUUID().getMostSignificantBits(),
entry.getUUID().getLeastSignificantBits(),
entry.getTitle(),
entry.getIconStandard().getIconId(),
entry.getCustomIcon().getUUID().getMostSignificantBits(),
entry.getCustomIcon().getUUID().getLeastSignificantBits()});
}
private static void populateEntryBaseVersion(Cursor cursor, PwEntry pwEntry, PwIconFactory iconFactory) {
pwEntry.setUUID(
new UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS))));
pwEntry.setTitle(cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE)));
PwIconStandard iconStandard = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)));
pwEntry.setIcon(iconStandard);
}
public static void populateEntry(Cursor cursor, PwEntryV3 pwEntry, PwIconFactory iconFactory) {
populateEntryBaseVersion(cursor, pwEntry, iconFactory);
}
public static void populateEntry(Cursor cursor, PwEntryV4 pwEntry, PwIconFactory iconFactory) {
populateEntryBaseVersion(cursor, pwEntry, iconFactory);
PwIconCustom iconCustom = iconFactory.getIcon(
new UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))));
pwEntry.setCustomIcon(iconCustom);
}
}

View File

@@ -22,8 +22,8 @@ package com.kunzisoft.keepass.database.iterator;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.SearchParameters;
import com.kunzisoft.keepass.database.SearchParametersV4;
import com.kunzisoft.keepass.database.search.SearchParameters;
import com.kunzisoft.keepass.database.search.SearchParametersV4;
import java.util.Iterator;

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.iterator;
import com.kunzisoft.keepass.database.PwEntryV3;
import com.kunzisoft.keepass.database.SearchParameters;
import com.kunzisoft.keepass.database.search.SearchParameters;
import java.util.NoSuchElementException;

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.database.iterator;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.SearchParametersV4;
import com.kunzisoft.keepass.database.search.SearchParametersV4;
import com.kunzisoft.keepass.database.security.ProtectedString;
import java.util.Iterator;
@@ -78,18 +78,19 @@ public class EntrySearchStringIteratorV4 extends EntrySearchStringIterator {
}
private boolean searchInField(String key) {
if (key.equals(PwEntryV4.STR_TITLE)) {
return sp.searchInTitles;
} else if (key.equals(PwEntryV4.STR_USERNAME)) {
return sp.searchInUserNames;
} else if (key.equals(PwEntryV4.STR_PASSWORD)) {
return sp.searchInPasswords;
} else if (key.equals(PwEntryV4.STR_URL)) {
return sp.searchInUrls;
} else if (key.equals(PwEntryV4.STR_NOTES)) {
return sp.searchInNotes;
} else {
return sp.searchInOther;
switch (key) {
case PwEntryV4.STR_TITLE:
return sp.searchInTitles;
case PwEntryV4.STR_USERNAME:
return sp.searchInUserNames;
case PwEntryV4.STR_PASSWORD:
return sp.searchInPasswords;
case PwEntryV4.STR_URL:
return sp.searchInUrls;
case PwEntryV4.STR_NOTES:
return sp.searchInNotes;
default:
return sp.searchInOther;
}
}

View File

@@ -350,10 +350,10 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
writeObject(PwDatabaseV4XML.ElemUuid, group.getUUID());
writeObject(PwDatabaseV4XML.ElemName, group.getName());
writeObject(PwDatabaseV4XML.ElemNotes, group.getNotes());
writeObject(PwDatabaseV4XML.ElemIcon, group.getIconStandard().iconId);
writeObject(PwDatabaseV4XML.ElemIcon, group.getIconStandard().getIconId());
if (!group.getCustomIcon().equals(PwIconCustom.ZERO)) {
writeObject(PwDatabaseV4XML.ElemCustomIconID, group.getCustomIcon().uuid);
writeObject(PwDatabaseV4XML.ElemCustomIconID, group.getCustomIcon().getUUID());
}
writeList(PwDatabaseV4XML.ElemTimes, group);
@@ -375,10 +375,10 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
xml.startTag(null, PwDatabaseV4XML.ElemEntry);
writeObject(PwDatabaseV4XML.ElemUuid, entry.getUUID());
writeObject(PwDatabaseV4XML.ElemIcon, entry.getIconStandard().iconId);
writeObject(PwDatabaseV4XML.ElemIcon, entry.getIconStandard().getIconId());
if (!entry.getCustomIcon().equals(PwIconCustom.ZERO)) {
writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.getCustomIcon().uuid);
writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.getCustomIcon().getUUID());
}
writeObject(PwDatabaseV4XML.ElemFgColor, entry.getForegroundColor());
@@ -701,8 +701,8 @@ public class PwDbV4Output extends PwDbOutput<PwDbHeaderV4> {
for (PwIconCustom icon : customIcons) {
xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem);
writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid);
writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String.valueOf(Base64Coder.encode(icon.imageData)));
writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.getUUID());
writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String.valueOf(Base64Coder.encode(icon.getImageData())));
xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem);
}

View File

@@ -84,7 +84,7 @@ public class PwEntryOutputV3 {
// Image ID
mOS.write(IMAGEID_FIELD_TYPE);
mOS.write(LONG_FOUR);
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIconStandard().iconId));
mOS.write(LEDataOutputStream.writeIntBuf(mPE.getIconStandard().getIconId()));
// Title
//byte[] title = mPE.title.getBytes("UTF-8");

View File

@@ -93,7 +93,7 @@ public class PwGroupOutputV3 {
// Image ID
mOS.write(IMAGEID_FIELD_TYPE);
mOS.write(IMAGEID_FIELD_SIZE);
mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIconStandard().iconId));
mOS.write(LEDataOutputStream.writeIntBuf(mPG.getIconStandard().getIconId()));
// Level
mOS.write(LEVEL_FIELD_TYPE);

View File

@@ -17,8 +17,10 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database;
package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.database.EntryHandler;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.iterator.EntrySearchStringIterator;
import java.util.Date;

View File

@@ -17,7 +17,10 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database;
package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.database.EntryHandler;
import com.kunzisoft.keepass.database.PwEntry;
import java.util.Date;
import java.util.List;

View File

@@ -17,8 +17,11 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database;
package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.utils.StrUtil;
import com.kunzisoft.keepass.utils.UuidUtil;

View File

@@ -17,8 +17,11 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database;
package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.database.EntryHandler;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.PwGroupV4;
import com.kunzisoft.keepass.utils.StrUtil;
import java.util.ArrayList;

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.search;
package com.kunzisoft.keepass.database.search;
import android.content.Context;
import android.content.SharedPreferences;
@@ -54,31 +54,33 @@ public class SearchDbHelper<PwDatabaseVersion extends PwDatabase<PwGroupSearch,
private boolean omitBackup() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mCtx);
return prefs.getBoolean(mCtx.getString(R.string.omitbackup_key), mCtx.getResources().getBoolean(R.bool.omitbackup_default));
}
public PwGroupSearch search(PwDatabaseVersion pm, String qStr) {
public PwGroupSearch search(PwDatabaseVersion pm, String qStr, int max) {
PwGroupSearch group = pm.createGroup();
group.setName(mCtx.getString(R.string.search_results));
group.setName("\"" + qStr + "\"");
group.setEntries(new ArrayList<>());
// Search all entries
Locale loc = Locale.getDefault();
qStr = qStr.toLowerCase(loc);
boolean isOmitBackup = omitBackup();
// TODO Search from the current group
Queue<PwGroupSearch> worklist = new LinkedList<>();
if (pm.getRootGroup() != null) {
worklist.add(pm.getRootGroup());
}
while (worklist.size() != 0) {
PwGroupSearch top = worklist.remove();
if (pm.isGroupSearchable(top, isOmitBackup)) {
for (PwEntrySearch entry : top.getChildEntries()) {
processEntries(entry, group.getChildEntries(), qStr, loc);
if (group.numbersOfChildEntries() >= max)
return group;
}
for (PwGroupSearch childGroup : top.getChildGroups()) {

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database;
package com.kunzisoft.keepass.database.search;
/**
* @author bpellin

View File

@@ -17,7 +17,7 @@
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database;
package com.kunzisoft.keepass.database.search;
public class SearchParametersV4 extends SearchParameters implements Cloneable {
public static SearchParametersV4 DEFAULT = new SearchParametersV4();

View File

@@ -221,7 +221,7 @@ public class IconDrawableFactory {
* @return The drawable
*/
private Drawable getIconDrawable(Context context, PwIconStandard icon, boolean isTint, int tintColor) {
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.iconId);
int resId = IconPackChooser.getSelectedIconPack(context).iconToResId(icon.getIconId());
return getIconDrawable(context, resId, isTint, tintColor);
}
@@ -289,14 +289,14 @@ public class IconDrawableFactory {
return blank;
}
Drawable draw = (Drawable) customIconMap.get(icon.uuid);
Drawable draw = (Drawable) customIconMap.get(icon.getUUID());
if (draw == null) {
if (icon.imageData == null) {
if (icon.getImageData() == null) {
return blank;
}
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.imageData, 0, icon.imageData.length);
Bitmap bitmap = BitmapFactory.decodeByteArray(icon.getImageData(), 0, icon.getImageData().length);
// Could not understand custom icon
if (bitmap == null) {
@@ -306,7 +306,7 @@ public class IconDrawableFactory {
bitmap = resize(bitmap);
draw = new BitmapDrawable(context.getResources(), bitmap);
customIconMap.put(icon.uuid, draw);
customIconMap.put(icon.getUUID(), draw);
}
return draw;

View File

@@ -1,114 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.search;
import android.app.SearchManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.activities.ListNodesActivity;
import com.kunzisoft.keepass.activities.ListNodesFragment;
import com.kunzisoft.keepass.database.PwGroup;
import com.kunzisoft.keepass.utils.MenuUtil;
import javax.annotation.Nullable;
public class SearchResultsActivity extends ListNodesActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutInflater().inflate(R.layout.search_results, null));
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitle(getString(R.string.search_label));
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
groupNameView = findViewById(R.id.group_name);
attachFragmentToContentView();
View notFoundView = findViewById(R.id.not_found_container);
View listContainer = findViewById(R.id.nodes_list_fragment_container);
if ( mCurrentGroup == null || mCurrentGroup.numbersOfChildEntries() < 1 ) {
listContainer.setVisibility(View.GONE);
notFoundView.setVisibility(View.VISIBLE);
} else {
listContainer.setVisibility(View.VISIBLE);
notFoundView.setVisibility(View.GONE);
}
}
@Override
protected void initializeListNodesFragment(PwGroup currentGroup) {
listNodesFragment = (ListNodesFragment) getSupportFragmentManager()
.findFragmentByTag(LIST_NODES_FRAGMENT_TAG);
// Directly get group and not id
if (listNodesFragment == null)
listNodesFragment = ListNodesFragment.newInstance(currentGroup, readOnly);
}
@Override
protected PwGroup retrieveCurrentGroup(@Nullable Bundle savedInstanceState) {
// Likely the app has been killed exit the activity
if ( ! database.getLoaded() ) {
finish();
}
return database.search(getSearchStr(getIntent()).trim());
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
MenuUtil.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.default_menu, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case android.R.id.home:
finish();
}
return super.onOptionsItemSelected(item);
}
private String getSearchStr(Intent queryIntent) {
// get and process search query here
final String queryAction = queryIntent.getAction();
if ( Intent.ACTION_SEARCH.equals(queryAction) ) {
return queryIntent.getStringExtra(SearchManager.QUERY);
}
return "";
}
}

View File

@@ -19,13 +19,12 @@
*/
package com.kunzisoft.keepass.utils;
import com.kunzisoft.keepass.database.EntrySearchV4;
import com.kunzisoft.keepass.database.PwDatabase;
import com.kunzisoft.keepass.database.PwDatabaseV4;
import com.kunzisoft.keepass.database.PwEntry;
import com.kunzisoft.keepass.database.PwEntryV4;
import com.kunzisoft.keepass.database.PwGroupV4;
import com.kunzisoft.keepass.database.SearchParametersV4;
import com.kunzisoft.keepass.database.search.EntrySearchV4;
import com.kunzisoft.keepass.database.search.SearchParametersV4;
import java.util.ArrayList;
import java.util.List;
@@ -158,7 +157,7 @@ public class SprEngineV4 {
List<PwEntryV4> list = new ArrayList<>();
// TODO type parameter
EntrySearchV4 entrySearchV4 = new EntrySearchV4((PwGroupV4) ctx.db.getRootGroup());
EntrySearchV4 entrySearchV4 = new EntrySearchV4(ctx.db.getRootGroup());
entrySearchV4.searchEntries(sp, list);
if (list.size() > 0) {

View File

@@ -189,7 +189,7 @@ public class AddNodeButtonView extends RelativeLayout {
}
}
public boolean isVisible() {
public boolean isEnable() {
return getVisibility() == VISIBLE;
}

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
*
* This file is part of KeePass DX.
*
* KeePass DX is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* KeePass DX is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.RelativeLayout;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.app.App;
public class GroupHeaderView extends RelativeLayout {
public GroupHeaderView(Context context) {
this(context, null);
}
public GroupHeaderView(Context context, AttributeSet attrs) {
super(context, attrs);
inflate(context);
}
private void inflate(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
assert inflater != null;
inflater.inflate(R.layout.group_header, this);
if (App.getDB().isReadOnly()) {
View readOnlyIndicator = findViewById(R.id.read_only);
readOnlyIndicator.setVisibility(VISIBLE);
}
}
}

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_in_right.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="100%p" android:toYDelta="0"
android:duration="@integer/animation_duration"/>
</set>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_in_left.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="-100%p" android:toYDelta="0"
android:duration="@integer/animation_duration"/>
</set>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_out_right.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="0" android:toYDelta="100%p"
android:duration="@integer/animation_duration"/>
</set>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/slide_out_left.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate android:fromYDelta="0" android:toYDelta="-100%p"
android:duration="@integer/animation_duration"/>
</set>

View File

@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft.
This file is part of KeePass DX.
KeePass DX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KeePass DX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:id="@+id/read_only"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/read_only"
android:visibility="gone"
style="@style/KeepassDXStyle.TextAppearance.WarningTextStyle" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginStart="@dimen/list_margin"
android:layout_marginEnd="@dimen/list_margin">
<android.support.v7.widget.AppCompatImageView android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:scaleType="fitXY" />
<android.support.v7.widget.AppCompatTextView android:id="@+id/group_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="@string/root"
android:maxLines="1"
style="@style/KeepassDXStyle.TextAppearance.TitleTextOnPrimary" />
</LinearLayout>
</LinearLayout>

View File

@@ -40,6 +40,7 @@
android:id="@+id/entry_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:lines="1"
style="@style/KeepassDXStyle.TextAppearance.Default"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"

View File

@@ -1,6 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nodes_list"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground" />
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/not_found_container"
android:layout_gravity="center"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone">
<android.support.v7.widget.AppCompatImageView
android:id="@+id/not_found_img"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/img_not_found"/>
<TextView
android:id="@+id/not_found_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_results"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/nodes_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:attr/windowBackground" />
</FrameLayout>

View File

@@ -56,13 +56,43 @@
app:popupTheme="?attr/toolbarPopupAppearance"
android:elevation="4dp"
tools:targetApi="lollipop">
<com.kunzisoft.keepass.view.GroupHeaderView
<LinearLayout
android:id="@+id/group_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_below="@+id/toolbar" />
android:layout_below="@+id/toolbar"
android:orientation="vertical">
<TextView android:id="@+id/search_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/search_results"
android:visibility="gone"
style="@style/KeepassDXStyle.TextAppearance.DefaultTextOnPrimary" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:layout_marginStart="@dimen/list_margin"
android:layout_marginEnd="@dimen/list_margin">
<android.support.v7.widget.AppCompatImageView android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center_vertical"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:scaleType="fitXY" />
<android.support.v7.widget.AppCompatTextView android:id="@+id/group_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_weight="1"
android:text="@string/root"
android:maxLines="1"
style="@style/KeepassDXStyle.TextAppearance.TitleTextOnPrimary" />
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.Toolbar>
</android.support.design.widget.CollapsingToolbarLayout>
@@ -70,10 +100,11 @@
<FrameLayout
android:id="@+id/nodes_list_fragment_container"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/toolbar" />
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:layout_below="@+id/toolbar"
android:background="?android:attr/windowBackground" />
<com.kunzisoft.keepass.view.AddNodeButtonView
android:id="@+id/add_node_button"

View File

@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 Jeremy Jamet / Kunzisoft.
This file is part of KeePass DX.
KeePass DX is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
KeePass DX is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with KeePass DX. If not, see <http://www.gnu.org/licenses/>.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/entry_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:minHeight="32dp"
android:background="?attr/colorPrimary" >
<android.support.v7.widget.AppCompatImageView android:id="@+id/entry_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginLeft="@dimen/default_margin"
android:layout_marginStart="@dimen/default_margin"
android:layout_marginRight="@dimen/default_margin"
android:layout_marginEnd="@dimen/default_margin"
android:scaleType="fitXY"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />
<TextView
android:id="@+id/entry_text"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:lines="1"
style="@style/KeepassDXStyle.TextAppearance.Inverse"
android:layout_centerVertical="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_toRightOf="@+id/entry_icon"
android:layout_toEndOf="@+id/entry_icon" />
</RelativeLayout>