/* This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin. Keepass2Android 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. Keepass2Android 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 Keepass2Android. If not, see . */ using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; using KeePassLib; using Android.Preferences; using KeePassLib.Interfaces; using KeePassLib.Serialization; using KeePassLib.Utility; using keepass2android.Io; using keepass2android.database.edit; using keepass2android.view; using Android.Graphics.Drawables; using Android.Support.V4.View; using CursorAdapter = Android.Support.V4.Widget.CursorAdapter; using Object = Java.Lang.Object; namespace keepass2android { public abstract class GroupBaseActivity : LockCloseActivity { public const String KeyEntry = "entry"; public const String KeyMode = "mode"; private ActivityDesign _design; public virtual void LaunchActivityForEntry(PwEntry pwEntry, int pos) { EntryActivity.Launch(this, pwEntry, pos, AppTask); } protected GroupBaseActivity() { _design = new ActivityDesign(this); } protected GroupBaseActivity(IntPtr javaReference, JniHandleOwnership transfer) : base(javaReference, transfer) { } protected override void OnSaveInstanceState(Bundle outState) { base.OnSaveInstanceState(outState); AppTask.ToBundle(outState); } public virtual void SetupNormalButtons() { SetNormalButtonVisibility(AddGroupEnabled, AddEntryEnabled); } protected virtual bool AddGroupEnabled { get { return App.Kp2a.GetDb().CanWrite; } } protected virtual bool AddEntryEnabled { get { return App.Kp2a.GetDb().CanWrite; } } public void SetNormalButtonVisibility(bool showAddGroup, bool showAddEntry) { //check for null in the following because the "empty" layouts may not have all views if (FindViewById(Resource.Id.bottom_bar) != null) FindViewById(Resource.Id.bottom_bar).Visibility = BottomBarAlwaysVisible ? ViewStates.Visible : ViewStates.Gone; if (FindViewById(Resource.Id.divider2) != null) FindViewById(Resource.Id.divider2).Visibility = BottomBarAlwaysVisible ? ViewStates.Visible : ViewStates.Gone; if (FindViewById(Resource.Id.fabCancelAddNew) != null) { FindViewById(Resource.Id.fabCancelAddNew).Visibility = ViewStates.Gone; FindViewById(Resource.Id.fabAddNewGroup).Visibility = ViewStates.Gone; FindViewById(Resource.Id.fabAddNewEntry).Visibility = ViewStates.Gone; FindViewById(Resource.Id.fabAddNew).Visibility = (showAddGroup || showAddEntry) ? ViewStates.Visible : ViewStates.Gone; } } public virtual bool BottomBarAlwaysVisible { get { return false; } } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); if (AppTask.TryGetFromActivityResult(data, ref AppTask)) { //make sure the app task is passed to the calling activity AppTask.SetActivityResult(this, KeePass.ExitNormal); } if (resultCode == Result.Ok) { String groupName = data.Extras.GetString(GroupEditActivity.KeyName); int groupIconId = data.Extras.GetInt(GroupEditActivity.KeyIconId); PwUuid groupCustomIconId = new PwUuid(MemUtil.HexStringToByteArray(data.Extras.GetString(GroupEditActivity.KeyCustomIconId))); String strGroupUuid = data.Extras.GetString(GroupEditActivity.KeyGroupUuid); GroupBaseActivity act = this; Handler handler = new Handler(); RunnableOnFinish task; if (strGroupUuid == null) { task = AddGroup.GetInstance(this, App.Kp2a, groupName, groupIconId, groupCustomIconId, Group, new RefreshTask(handler, this), false); } else { PwUuid groupUuid = new PwUuid(MemUtil.HexStringToByteArray(strGroupUuid)); task = new EditGroup(this, App.Kp2a, groupName, (PwIcon)groupIconId, groupCustomIconId, App.Kp2a.GetDb().Groups[groupUuid], new RefreshTask(handler, this)); } ProgressTask pt = new ProgressTask(App.Kp2a, act, task); pt.Run(); } if (resultCode == KeePass.ExitCloseAfterTaskComplete) { AppTask.SetActivityResult(this, KeePass.ExitCloseAfterTaskComplete); Finish(); } if (resultCode == KeePass.ExitReloadDb) { AppTask.SetActivityResult(this, KeePass.ExitReloadDb); Finish(); } } private ISharedPreferences _prefs; protected PwGroup Group; internal AppTask AppTask; private String strCachedGroupUuid = null; private IMenuItem _offlineItem; private IMenuItem _onlineItem; private IMenuItem _syncItem; public String UuidGroup { get { if (strCachedGroupUuid == null) { strCachedGroupUuid = MemUtil.ByteArrayToHexString(Group.Uuid.UuidBytes); } return strCachedGroupUuid; } } protected override void OnResume() { base.OnResume(); _design.ReapplyTheme(); AppTask.StartInGroupActivity(this); AppTask.SetupGroupBaseActivityButtons(this); RefreshIfDirty(); } public override bool OnSearchRequested() { Intent i = new Intent(this, typeof(SearchActivity)); AppTask.ToIntent(i); StartActivityForResult(i, 0); return true; } public void RefreshIfDirty() { Database db = App.Kp2a.GetDb(); if (db.Dirty.Contains(Group)) { db.Dirty.Remove(Group); ListAdapter.NotifyDataSetChanged(); } } public BaseAdapter ListAdapter { get { return (BaseAdapter)FragmentManager.FindFragmentById(Resource.Id.list_fragment).ListAdapter; } } public virtual bool IsSearchResult { get { return false; } } protected override void OnCreate(Bundle savedInstanceState) { _design.ApplyTheme(); base.OnCreate(savedInstanceState); Android.Util.Log.Debug("KP2A", "Creating GBA"); AppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent); // Likely the app has been killed exit the activity if (!App.Kp2a.GetDb().Loaded) { Finish(); return; } _prefs = PreferenceManager.GetDefaultSharedPreferences(this); SetContentView(ContentResourceId); if (FindViewById(Resource.Id.fabCancelAddNew) != null) { FindViewById(Resource.Id.fabAddNew).Click += (sender, args) => { FindViewById(Resource.Id.fabCancelAddNew).Visibility = ViewStates.Visible; FindViewById(Resource.Id.fabAddNewGroup).Visibility = AddGroupEnabled ? ViewStates.Visible : ViewStates.Gone; FindViewById(Resource.Id.fabAddNewEntry).Visibility = AddEntryEnabled ? ViewStates.Visible : ViewStates.Gone; FindViewById(Resource.Id.fabAddNew).Visibility = ViewStates.Gone; }; FindViewById(Resource.Id.fabCancelAddNew).Click += (sender, args) => { FindViewById(Resource.Id.fabCancelAddNew).Visibility = ViewStates.Gone; FindViewById(Resource.Id.fabAddNewGroup).Visibility = ViewStates.Gone; FindViewById(Resource.Id.fabAddNewEntry).Visibility = ViewStates.Gone; FindViewById(Resource.Id.fabAddNew).Visibility = ViewStates.Visible; }; } if (FindViewById(Resource.Id.cancel_insert_element) != null) { FindViewById(Resource.Id.cancel_insert_element).Click += (sender, args) => StopMovingElements(); FindViewById(Resource.Id.insert_element).Click += (sender, args) => InsertElements(); Util.MoveBottomBarButtons(Resource.Id.cancel_insert_element, Resource.Id.insert_element, Resource.Id.bottom_bar, this); } SetResult(KeePass.ExitNormal); } protected virtual int ContentResourceId { get { return Resource.Layout.group; } } private void InsertElements() { MoveElementsTask moveElementsTask = (MoveElementsTask)AppTask; IEnumerable elementsToMove = moveElementsTask.Uuids.Select(uuid => App.Kp2a.GetDb().KpDatabase.RootGroup.FindObject(uuid, true, null)); var moveElement = new MoveElements(elementsToMove.ToList(), Group, this, App.Kp2a, new ActionOnFinish((success, message) => { StopMovingElements(); if (!String.IsNullOrEmpty(message)) Toast.MakeText(this, message, ToastLength.Long).Show(); })); var progressTask = new ProgressTask(App.Kp2a, this, moveElement); progressTask.Run(); } protected void SetGroupTitle() { String name = Group.Name; String titleText; bool clickable = (Group != null) && (Group.IsVirtual == false) && (Group.ParentGroup != null); if (!String.IsNullOrEmpty(name)) { titleText = name; } else { titleText = GetText(Resource.String.root); } SupportActionBar.Title = titleText; if (clickable) { SupportActionBar.SetHomeButtonEnabled(true); SupportActionBar.SetDisplayHomeAsUpEnabled(true); SupportActionBar.SetDisplayShowHomeEnabled(true); } } protected void SetGroupIcon() { if (Group != null) { Drawable drawable = App.Kp2a.GetDb().DrawableFactory.GetIconDrawable(this, App.Kp2a.GetDb().KpDatabase, Group.IconId, Group.CustomIconUuid, true); SupportActionBar.SetDisplayShowHomeEnabled(true); //SupportActionBar.SetIcon(drawable); } } class SuggestionListener : Java.Lang.Object, SearchView.IOnSuggestionListener, Android.Support.V7.Widget.SearchView.IOnSuggestionListener { private readonly CursorAdapter _suggestionsAdapter; private readonly GroupBaseActivity _activity; private readonly IMenuItem _searchItem; public SuggestionListener(Android.Support.V4.Widget.CursorAdapter suggestionsAdapter, GroupBaseActivity activity, IMenuItem searchItem) { _suggestionsAdapter = suggestionsAdapter; _activity = activity; _searchItem = searchItem; } public bool OnSuggestionClick(int position) { var cursor = _suggestionsAdapter.Cursor; cursor.MoveToPosition(position); string entryIdAsHexString = cursor.GetString(cursor.GetColumnIndexOrThrow(SearchManager.SuggestColumnIntentDataId)); EntryActivity.Launch(_activity, App.Kp2a.GetDb().Entries[new PwUuid(MemUtil.HexStringToByteArray(entryIdAsHexString))], -1, _activity.AppTask); return true; } public bool OnSuggestionSelect(int position) { return false; } } class OnQueryTextListener : Java.Lang.Object, Android.Support.V7.Widget.SearchView.IOnQueryTextListener { private readonly GroupBaseActivity _activity; public OnQueryTextListener(GroupBaseActivity activity) { _activity = activity; } public bool OnQueryTextChange(string newText) { return false; } public bool OnQueryTextSubmit(string query) { if (String.IsNullOrEmpty(query)) return false; //let the default happen Intent searchIntent = new Intent(_activity, typeof(search.SearchResults)); searchIntent.SetAction(Intent.ActionSearch); //currently not necessary to set because SearchResults doesn't care, but let's be as close to the default as possible searchIntent.PutExtra(SearchManager.Query, query); //forward appTask: _activity.AppTask.ToIntent(searchIntent); _activity.StartActivityForResult(searchIntent, 0); return true; } } public override bool OnCreateOptionsMenu(IMenu menu) { MenuInflater inflater = MenuInflater; inflater.Inflate(Resource.Menu.group, menu); var searchManager = (SearchManager)GetSystemService(Context.SearchService); IMenuItem searchItem = menu.FindItem(Resource.Id.menu_search); var view = MenuItemCompat.GetActionView(searchItem); var searchView = view.JavaCast(); searchView.SetSearchableInfo(searchManager.GetSearchableInfo(ComponentName)); searchView.SetOnSuggestionListener(new SuggestionListener(searchView.SuggestionsAdapter, this, searchItem)); searchView.SetOnQueryTextListener(new OnQueryTextListener(this)); ActionBar.LayoutParams lparams = new ActionBar.LayoutParams(ActionBar.LayoutParams.MatchParent, ActionBar.LayoutParams.MatchParent); searchView.LayoutParameters = lparams; _syncItem = menu.FindItem(Resource.Id.menu_sync); _offlineItem = menu.FindItem(Resource.Id.menu_work_offline); _onlineItem = menu.FindItem(Resource.Id.menu_work_online); UpdateOfflineModeMenu(); return base.OnCreateOptionsMenu(menu); } private void UpdateOfflineModeMenu() { try { if (_syncItem != null) { if (App.Kp2a.GetDb().Ioc.IsLocalFile()) _syncItem.SetVisible(false); else _syncItem.SetVisible(!App.Kp2a.OfflineMode); } if (App.Kp2a.GetFileStorage(App.Kp2a.GetDb().Ioc) is IOfflineSwitchable) { if (_offlineItem != null) _offlineItem.SetVisible(App.Kp2a.OfflineMode == false); if (_onlineItem != null) _onlineItem.SetVisible(App.Kp2a.OfflineMode); } else { if (_offlineItem != null) _offlineItem.SetVisible(false); if (_onlineItem != null) _onlineItem.SetVisible(false); } } catch (Exception e) { Kp2aLog.LogUnexpectedError(new Exception("Cannot UpdateOfflineModeMenu " + (App.Kp2a == null) + " " + ((App.Kp2a == null) || (App.Kp2a.GetDb() == null)) + " " + (((App.Kp2a == null) || (App.Kp2a.GetDb() == null) || (App.Kp2a.GetDb().Ioc == null)) + " " + (_syncItem != null) + " " + (_offlineItem != null) + " " + (_onlineItem != null)))); } } public override bool OnPrepareOptionsMenu(IMenu menu) { if (!base.OnPrepareOptionsMenu(menu)) { return false; } Util.PrepareDonateOptionMenu(menu, this); return true; } public override bool OnOptionsItemSelected(IMenuItem item) { switch (item.ItemId) { case Resource.Id.menu_donate: return Util.GotoDonateUrl(this); case Resource.Id.menu_lock: App.Kp2a.LockDatabase(); return true; case Resource.Id.menu_search: case Resource.Id.menu_search_advanced: OnSearchRequested(); return true; case Resource.Id.menu_app_settings: DatabaseSettingsActivity.Launch(this); return true; case Resource.Id.menu_sync: Synchronize(); return true; case Resource.Id.menu_work_offline: App.Kp2a.OfflineMode = App.Kp2a.OfflineModePreference = true; UpdateOfflineModeMenu(); return true; case Resource.Id.menu_work_online: App.Kp2a.OfflineMode = App.Kp2a.OfflineModePreference = false; UpdateOfflineModeMenu(); Synchronize(); return true; case Resource.Id.menu_sort: ChangeSort(); return true; case Android.Resource.Id.Home: //Currently the action bar only displays the home button when we come from a previous activity. //So we can simply Finish. See this page for information on how to do this in more general (future?) cases: //http://developer.android.com/training/implementing-navigation/ancestral.html AppTask.SetActivityResult(this, KeePass.ExitNormal); Finish(); //OverridePendingTransition(Resource.Animation.anim_enter_back, Resource.Animation.anim_leave_back); return true; } return base.OnOptionsItemSelected(item); } public class SyncOtpAuxFile : RunnableOnFinish { private readonly IOConnectionInfo _ioc; public SyncOtpAuxFile(IOConnectionInfo ioc) : base(null) { _ioc = ioc; } public override void Run() { StatusLogger.UpdateMessage(UiStringKey.SynchronizingOtpAuxFile); try { //simply open the file. The file storage does a complete sync. using (App.Kp2a.GetOtpAuxFileStorage(_ioc).OpenFileForRead(_ioc)) { } Finish(true); } catch (Exception e) { Finish(false, e.Message); } } } private void Synchronize() { var filestorage = App.Kp2a.GetFileStorage(App.Kp2a.GetDb().Ioc); RunnableOnFinish task; OnFinish onFinish = new ActionOnFinish((success, message) => { if (!String.IsNullOrEmpty(message)) Toast.MakeText(this, message, ToastLength.Long).Show(); // Tell the adapter to refresh it's list BaseAdapter adapter = (BaseAdapter)ListAdapter; adapter.NotifyDataSetChanged(); if (App.Kp2a.GetDb().OtpAuxFileIoc != null) { var task2 = new SyncOtpAuxFile(App.Kp2a.GetDb().OtpAuxFileIoc); new ProgressTask(App.Kp2a, this, task2).Run(); } }); if (filestorage is CachingFileStorage) { task = new SynchronizeCachedDatabase(this, App.Kp2a, onFinish); } else { task = new CheckDatabaseForChanges(this, App.Kp2a, onFinish); } var progressTask = new ProgressTask(App.Kp2a, this, task); progressTask.Run(); } public override void OnBackPressed() { AppTask.SetActivityResult(this, KeePass.ExitNormal); base.OnBackPressed(); } private void ChangeSort() { var sortOrderManager = new GroupViewSortOrderManager(this); IEnumerable sortOptions = sortOrderManager.SortOrders.Select( o => GetString(o.ResourceId) ); int selectedBefore = sortOrderManager.GetCurrentSortOrderIndex(); new AlertDialog.Builder(this) .SetSingleChoiceItems(sortOptions.ToArray(), selectedBefore, (sender, args) => { int selectedAfter = args.Which; sortOrderManager.SetNewSortOrder(selectedAfter); // Refresh menu titles ActivityCompat.InvalidateOptionsMenu(this); // Mark all groups as dirty now to refresh them on load Database db = App.Kp2a.GetDb(); db.MarkAllGroupsAsDirty(); // We'll manually refresh this group so we can remove it db.Dirty.Remove(Group); // Tell the adapter to refresh it's list BaseAdapter adapter = (BaseAdapter)ListAdapter; adapter.NotifyDataSetChanged(); }) .SetPositiveButton(Android.Resource.String.Ok, (sender, args) => ((Dialog)sender).Dismiss()) .Show(); } public class RefreshTask : OnFinish { readonly GroupBaseActivity _act; public RefreshTask(Handler handler, GroupBaseActivity act) : base(handler) { _act = act; } public override void Run() { if (Success) { _act.RefreshIfDirty(); } else { DisplayMessage(_act); } } } public class AfterDeleteGroup : OnFinish { readonly GroupBaseActivity _act; public AfterDeleteGroup(Handler handler, GroupBaseActivity act) : base(handler) { _act = act; } public override void Run() { if (Success) { _act.RefreshIfDirty(); } else { Handler.Post(() => { Toast.MakeText(_act, "Unrecoverable error: " + Message, ToastLength.Long).Show(); }); App.Kp2a.LockDatabase(false); } } } public bool IsBeingMoved(PwUuid uuid) { MoveElementsTask moveElementsTask = AppTask as MoveElementsTask; if (moveElementsTask != null) { if (moveElementsTask.Uuids.Any(uuidMoved => uuidMoved.Equals(uuid))) return true; } return false; } public void StartTask(AppTask task) { AppTask = task; task.StartInGroupActivity(this); } public void StartMovingElements() { ShowInsertElementsButtons(); BaseAdapter adapter = (BaseAdapter)ListAdapter; adapter.NotifyDataSetChanged(); } public void ShowInsertElementsButtons() { FindViewById(Resource.Id.fabCancelAddNew).Visibility = ViewStates.Gone; FindViewById(Resource.Id.fabAddNewGroup).Visibility = ViewStates.Gone; FindViewById(Resource.Id.fabAddNewEntry).Visibility = ViewStates.Gone; FindViewById(Resource.Id.fabAddNew).Visibility = ViewStates.Gone; FindViewById(Resource.Id.bottom_bar).Visibility = ViewStates.Visible; FindViewById(Resource.Id.divider2).Visibility = ViewStates.Visible; } public void StopMovingElements() { try { MoveElementsTask moveElementsTask = (MoveElementsTask)AppTask; foreach (var uuid in moveElementsTask.Uuids) { IStructureItem elementToMove = App.Kp2a.GetDb().KpDatabase.RootGroup.FindObject(uuid, true, null); if (elementToMove.ParentGroup != Group) App.Kp2a.GetDb().Dirty.Add(elementToMove.ParentGroup); } } catch (Exception e) { //don't crash if adding to dirty fails but log the exception: Kp2aLog.LogUnexpectedError(e); } AppTask = new NullTask(); AppTask.SetupGroupBaseActivityButtons(this); BaseAdapter adapter = (BaseAdapter)ListAdapter; adapter.NotifyDataSetChanged(); } public void EditGroup(PwGroup pwGroup) { GroupEditActivity.Launch(this, pwGroup.ParentGroup, pwGroup); } } public class GroupListFragment : ListFragment, AbsListView.IMultiChoiceModeListener { private ActionMode _mode; private int _statusBarColor; public override void OnActivityCreated(Bundle savedInstanceState) { base.OnActivityCreated(savedInstanceState); if (App.Kp2a.GetDb().CanWrite) { ListView.ChoiceMode = ChoiceMode.MultipleModal; ListView.SetMultiChoiceModeListener(this); ListView.ItemLongClick += delegate(object sender, AdapterView.ItemLongClickEventArgs args) { ListView.SetItemChecked(args.Position, true); }; } ListView.ItemClick += (sender, args) => ((GroupListItemView)args.View).OnClick(); StyleListView(); } protected void StyleListView() { ListView lv = ListView; lv.ScrollBarStyle = ScrollbarStyles.InsideInset; lv.TextFilterEnabled = true; lv.Divider = null; } public bool OnActionItemClicked(ActionMode mode, IMenuItem item) { var listView = FragmentManager.FindFragmentById(Resource.Id.list_fragment).ListView; var checkedItemPositions = listView.CheckedItemPositions; List checkedItems = new List(); for (int i = 0; i < checkedItemPositions.Size(); i++) { if (checkedItemPositions.ValueAt(i)) { checkedItems.Add(((PwGroupListAdapter)ListAdapter).GetItemAtPosition(checkedItemPositions.KeyAt(i))); } } //shouldn't happen, just in case... if (!checkedItems.Any()) { return false; } Handler handler = new Handler(); switch (item.ItemId) { case Resource.Id.menu_delete: DeleteMultipleItems task = new DeleteMultipleItems((GroupBaseActivity)Activity, App.Kp2a.GetDb(), checkedItems, new GroupBaseActivity.RefreshTask(handler, ((GroupBaseActivity)Activity)), App.Kp2a); task.Start(); break; case Resource.Id.menu_move: var navMove = new NavigateToFolderAndLaunchMoveElementTask(checkedItems.First().ParentGroup, checkedItems.Select(i => i.Uuid).ToList(), ((GroupBaseActivity)Activity).IsSearchResult); ((GroupBaseActivity)Activity).StartTask(navMove); break; case Resource.Id.menu_copy: var copyTask = new CopyEntry((GroupBaseActivity)Activity, App.Kp2a, (PwEntry) checkedItems.First(), new GroupBaseActivity.RefreshTask(handler, ((GroupBaseActivity)Activity))); ProgressTask pt = new ProgressTask(App.Kp2a, Activity, copyTask); pt.Run(); break; case Resource.Id.menu_navigate: NavigateToFolder navNavigate = new NavigateToFolder(checkedItems.First().ParentGroup, true); ((GroupBaseActivity)Activity).StartTask(navNavigate); break; case Resource.Id.menu_edit: GroupEditActivity.Launch(Activity, checkedItems.First().ParentGroup, (PwGroup)checkedItems.First()); break; default: return false; } listView.ClearChoices(); ((BaseAdapter)ListAdapter).NotifyDataSetChanged(); if (_mode != null) mode.Finish(); return true; } public bool OnCreateActionMode(ActionMode mode, IMenu menu) { MenuInflater inflater = Activity.MenuInflater; inflater.Inflate(Resource.Menu.group_entriesselected, menu); //mode.Title = "Select Items"; Android.Util.Log.Debug("KP2A", "Create action mode" + mode); ((PwGroupListAdapter)ListView.Adapter).InActionMode = true; ((PwGroupListAdapter)ListView.Adapter).NotifyDataSetChanged(); _mode = mode; if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop) { _statusBarColor = Activity.Window.StatusBarColor; Activity.Window.SetStatusBarColor(Activity.Resources.GetColor(Resource.Color.appAccentColorDark)); } return true; } public void OnDestroyActionMode(ActionMode mode) { Android.Util.Log.Debug("KP2A", "Destroy action mode" + mode); ((PwGroupListAdapter)ListView.Adapter).InActionMode = false; ((PwGroupListAdapter)ListView.Adapter).NotifyDataSetChanged(); _mode = null; if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop) { Activity.Window.SetStatusBarColor(new Android.Graphics.Color(_statusBarColor)); } } public bool OnPrepareActionMode(ActionMode mode, IMenu menu) { Android.Util.Log.Debug("KP2A", "Prepare action mode" + mode); ((PwGroupListAdapter)ListView.Adapter).InActionMode = mode != null; ((PwGroupListAdapter)ListView.Adapter).NotifyDataSetChanged(); return true; } public void OnItemCheckedStateChanged(ActionMode mode, int position, long id, bool @checked) { var menuItem = mode.Menu.FindItem(Resource.Id.menu_edit); if (menuItem != null) { menuItem.SetVisible(IsOnlyOneGroupChecked()); } menuItem = mode.Menu.FindItem(Resource.Id.menu_navigate); if (menuItem != null) { menuItem.SetVisible(((GroupBaseActivity)Activity).IsSearchResult && IsOnlyOneItemChecked()); } menuItem = mode.Menu.FindItem(Resource.Id.menu_copy); if (menuItem != null) { menuItem.SetVisible(IsOnlyOneEntryChecked()); } } private bool IsOnlyOneGroupChecked() { var checkedItems = ListView.CheckedItemPositions; bool hadCheckedGroup = false; if (checkedItems != null) { for (int i = 0; i < checkedItems.Size(); i++) { if (checkedItems.ValueAt(i)) { if (hadCheckedGroup) { return false; } if (((PwGroupListAdapter)ListAdapter).IsGroupAtPosition(checkedItems.KeyAt(i))) { hadCheckedGroup = true; } else { return false; } } } } return hadCheckedGroup; } private bool IsOnlyOneItemChecked() { var checkedItems = ListView.CheckedItemPositions; bool hadCheckedItem = false; if (checkedItems != null) { for (int i = 0; i < checkedItems.Size(); i++) { if (checkedItems.ValueAt(i)) { if (hadCheckedItem) { return false; } hadCheckedItem = true; } } } return hadCheckedItem; } private bool IsOnlyOneEntryChecked() { return IsOnlyOneItemChecked() && !IsOnlyOneGroupChecked(); } } }