<NME> CacheMenuHandler.java
<BEF> package cgeo.geocaching;

import cgeo.calendar.ICalendar;
import cgeo.geocaching.apps.cache.navi.NavigationAppFactory;
import cgeo.geocaching.geopoint.GeopointFormatter;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.ui.AbstractUIFactory;
import cgeo.geocaching.ui.dialog.Dialogs;
import cgeo.geocaching.utils.ProcessUtils;

import org.apache.commons.lang3.StringUtils;

import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.view.Menu;
import android.view.MenuItem;

import java.util.Date;

/**
 * Shared menu handling for all activities having menu items related to a cache.
 *
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.storage.DataStore;
import cgeo.geocaching.ui.AbstractUIFactory;
import cgeo.geocaching.ui.NavigationActionProvider;

import android.app.Activity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.MenuItemCompat;
import androidx.fragment.app.Fragment;

import java.util.Collections;

/**
 * Shared menu handling for all activities having menu items related to a cache. <br>
 * TODO: replace by a fragment
 */
public final class CacheMenuHandler extends AbstractUIFactory {

    private CacheMenuHandler() {
        // utility class
    }

    /**
     * Methods to be implemented by the activity to react to the cache menu selections.
     */
    interface ActivityInterface {
        void navigateTo();
                cache.shareCache(activity, res);
                return true;
            case R.id.menu_calendar:
                addToCalendarWithIntent(activity, cache);
                return true;
            default:
                return false;
    public static boolean onMenuItemSelected(final MenuItem item, @NonNull final CacheMenuHandler.ActivityInterface activityInterface, final Geocache cache, @Nullable final Runnable notifyDataSetChanged, final boolean fromPopup) {
        final Activity activity;
        if (activityInterface instanceof Activity) {
            activity = (Activity) activityInterface;
        } else {
            activity = ((Fragment) activityInterface).getActivity();
        }

        final int menuItem = item.getItemId();
        if (menuItem == R.id.menu_default_navigation) {
            activityInterface.navigateTo();
            return true;
        } else if (menuItem == R.id.menu_navigate) {
            final NavigationSelectionActionProvider navigationProvider = (NavigationSelectionActionProvider) MenuItemCompat.getActionProvider(item);
            if (navigationProvider == null || fromPopup) {
        activity.getMenuInflater().inflate(R.menu.cache_options, menu);
        onPrepareOptionsMenu(menu, cache);
    }

    private static void addToCalendarWithIntent(final Activity activity, final Geocache cache) {
        final boolean calendarAddOnAvailable = ProcessUtils.isIntentAvailable(ICalendar.INTENT, Uri.parse(ICalendar.URI_SCHEME + "://" + ICalendar.URI_HOST));

        if (calendarAddOnAvailable) {
            final Date hiddenDate = cache.getHiddenDate();
            final Parameters params = new Parameters(
                    ICalendar.PARAM_NAME, cache.getName(),
                    ICalendar.PARAM_NOTE, StringUtils.defaultString(cache.getPersonalNote()),
                    ICalendar.PARAM_HIDDEN_DATE, hiddenDate != null ? String.valueOf(hiddenDate.getTime()) : StringUtils.EMPTY,
                    ICalendar.PARAM_URL, StringUtils.defaultString(cache.getUrl()),
                    ICalendar.PARAM_COORDS, cache.getCoords() == null ? "" : cache.getCoords().format(GeopointFormatter.Format.LAT_LON_DECMINUTE_RAW),
                    ICalendar.PARAM_LOCATION, StringUtils.defaultString(cache.getLocation()),
                    ICalendar.PARAM_SHORT_DESC, StringUtils.defaultString(cache.getShortDescription()),
                    ICalendar.PARAM_START_TIME_MINUTES, StringUtils.defaultString(cache.guessEventTimeMinutes())
                    );

            activity.startActivity(new Intent(ICalendar.INTENT,
                    Uri.parse(ICalendar.URI_SCHEME + "://" + ICalendar.URI_HOST + "?" + params.toString())));
        } else {
            // Inform user the calendar add-on is not installed and let them get it from Google Play
            Dialogs.confirmYesNo(activity, R.string.addon_missing_title, new StringBuilder(res.getString(R.string.helper_calendar_missing))
                    .append(' ')
                    .append(res.getString(R.string.addon_download_prompt))
                    .toString(), new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int id) {
                    final Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setData(Uri.parse(ICalendar.CALENDAR_ADDON_URI));
                    activity.startActivity(intent);
                }
            });
        }
    }

}
        } else if (menuItem == R.id.menu_caches_around || menuItem == R.id.menu_caches_around_from_popup) {
            activityInterface.cachesAround();
            return true;
        } else if (menuItem == R.id.menu_upload_bookmarklist) {
            BookmarkUtils.askAndUploadCachesToBookmarkList(activity, Collections.singletonList(cache));
            return true;
        } else if (menuItem == R.id.menu_show_in_browser) {
            cache.openInBrowser(activity);
            return true;
        } else if (menuItem == R.id.menu_log_in_browser) {
            cache.openCreateNewLogInBrowser(activity);
            return true;
        } else if (menuItem == R.id.menu_share || menuItem == R.id.menu_share_from_popup) {
            cache.shareCache(activity, res);
            return true;
        } else if (menuItem == R.id.menu_calendar) {
            CalendarAdder.addToCalendar(activity, cache);
            return true;
        } else if (menuItem == R.id.menu_set_found) {
            setFoundState(activity, cache, true, false, notifyDataSetChanged);
        } else if (menuItem == R.id.menu_set_DNF) {
            setFoundState(activity, cache, false, true, notifyDataSetChanged);
        } else if (menuItem == R.id.menu_reset_foundstate) {
            setFoundState(activity, cache, false, false, notifyDataSetChanged);
        }
        return false;
    }

    private static void setFoundState(final Activity activity, final Geocache cache, final boolean foundState, final boolean dnfState, @Nullable final Runnable notifyDataSetChanged) {
        cache.setFound(foundState);
        cache.setDNF(dnfState);
        if (!cache.isOffline()) {
            // store to default list if not yet stored
            cache.setLists(Collections.singleton(StoredList.STANDARD_LIST_ID));
        }
        DataStore.saveCache(cache, LoadFlags.SAVE_ALL);
        Toast.makeText(activity, R.string.cache_foundstate_updated, Toast.LENGTH_SHORT).show();
        if (notifyDataSetChanged != null) {
            notifyDataSetChanged.run();
        }
    }

    public static void onPrepareOptionsMenu(final Menu menu, final Geocache cache, final boolean fromPopup) {
        if (cache == null) {
            return;
        }
        final boolean hasCoords = cache.getCoords() != null;
        // top level menu items
        menu.findItem(R.id.menu_default_navigation).setVisible(hasCoords);
        menu.findItem(R.id.menu_default_navigation).setTitle(NavigationAppFactory.getDefaultNavigationApplication().getName());
        menu.findItem(R.id.menu_navigate).setVisible(hasCoords);
        if (!fromPopup) {
            NavigationSelectionActionProvider.initialize(menu.findItem(R.id.menu_navigate), cache);
        }
        menu.findItem(R.id.menu_log_visit).setVisible(cache.supportsLogging() && !Settings.getLogOffline());
        menu.findItem(R.id.menu_log_in_browser).setVisible(cache.supportsLoggingOnline());
        menu.findItem(R.id.menu_log_visit_offline).setVisible(cache.supportsLogging() && Settings.getLogOffline());
        menu.findItem(R.id.menu_set_found).setVisible(cache.supportsSettingFoundState() && !cache.isFound());
        menu.findItem(R.id.menu_set_DNF).setVisible(cache.supportsSettingFoundState() && !cache.isDNF());
        menu.findItem(R.id.menu_reset_foundstate).setVisible(cache.supportsSettingFoundState() && (cache.isFound() || cache.isDNF()));
        // some connectors don't support URL - we don't need "open in browser" for those caches
        menu.findItem(R.id.menu_show_in_browser).setVisible(cache.getUrl() != null);
        // submenu share / export
        menu.findItem(fromPopup ? R.id.menu_share_from_popup : R.id.menu_share).setVisible(!InternalConnector.getInstance().canHandle(cache.getGeocode()));
        // submenu advanced
        menu.findItem(R.id.menu_calendar).setVisible(cache.canBeAddedToCalendar());
        menu.findItem(fromPopup ? R.id.menu_caches_around_from_popup : R.id.menu_caches_around).setVisible(hasCoords);
        menu.findItem(R.id.menu_upload_bookmarklist).setVisible(Settings.isGCConnectorActive() && Settings.isGCPremiumMember() && ConnectorFactory.getConnector(cache) instanceof GCConnector);
    }

    public static void addMenuItems(final MenuInflater inflater, final Menu menu, final Geocache cache, final boolean fromPopup) {
        inflater.inflate(R.menu.cache_options, menu);
        onPrepareOptionsMenu(menu, cache, fromPopup);
    }

    public static void addMenuItems(final Activity activity, final Menu menu, final Geocache cache) {
        addMenuItems(activity.getMenuInflater(), menu, cache, false);
    }

    public static void initDefaultNavigationMenuItem(final Menu menu, final INavigationSource navigationSource) {
        final MenuItem defaultNavigationMenuItem = menu.findItem(R.id.menu_default_navigation);
        final NavigationActionProvider defaultNavAction = (NavigationActionProvider) MenuItemCompat.getActionProvider(defaultNavigationMenuItem);
        if (defaultNavAction != null) {
            defaultNavAction.setNavigationSource(navigationSource);
        }
    }
}

<MSG> refactoring: move calendar addon code into a single class
<DFF> @@ -1,24 +1,13 @@
 package cgeo.geocaching;
 
-import cgeo.calendar.ICalendar;
+import cgeo.calendar.CalendarAddon;
 import cgeo.geocaching.apps.cache.navi.NavigationAppFactory;
-import cgeo.geocaching.geopoint.GeopointFormatter;
-import cgeo.geocaching.network.Parameters;
 import cgeo.geocaching.ui.AbstractUIFactory;
-import cgeo.geocaching.ui.dialog.Dialogs;
-import cgeo.geocaching.utils.ProcessUtils;
-
-import org.apache.commons.lang3.StringUtils;
 
 import android.app.Activity;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.net.Uri;
 import android.view.Menu;
 import android.view.MenuItem;
 
-import java.util.Date;
-
 /**
  * Shared menu handling for all activities having menu items related to a cache.
  *
@@ -58,7 +47,7 @@ public class CacheMenuHandler extends AbstractUIFactory {
                 cache.shareCache(activity, res);
                 return true;
             case R.id.menu_calendar:
-                addToCalendarWithIntent(activity, cache);
+                CalendarAddon.addToCalendarWithIntent(activity, cache);
                 return true;
             default:
                 return false;
@@ -80,39 +69,4 @@ public class CacheMenuHandler extends AbstractUIFactory {
         activity.getMenuInflater().inflate(R.menu.cache_options, menu);
         onPrepareOptionsMenu(menu, cache);
     }
-
-    private static void addToCalendarWithIntent(final Activity activity, final Geocache cache) {
-        final boolean calendarAddOnAvailable = ProcessUtils.isIntentAvailable(ICalendar.INTENT, Uri.parse(ICalendar.URI_SCHEME + "://" + ICalendar.URI_HOST));
-
-        if (calendarAddOnAvailable) {
-            final Date hiddenDate = cache.getHiddenDate();
-            final Parameters params = new Parameters(
-                    ICalendar.PARAM_NAME, cache.getName(),
-                    ICalendar.PARAM_NOTE, StringUtils.defaultString(cache.getPersonalNote()),
-                    ICalendar.PARAM_HIDDEN_DATE, hiddenDate != null ? String.valueOf(hiddenDate.getTime()) : StringUtils.EMPTY,
-                    ICalendar.PARAM_URL, StringUtils.defaultString(cache.getUrl()),
-                    ICalendar.PARAM_COORDS, cache.getCoords() == null ? "" : cache.getCoords().format(GeopointFormatter.Format.LAT_LON_DECMINUTE_RAW),
-                    ICalendar.PARAM_LOCATION, StringUtils.defaultString(cache.getLocation()),
-                    ICalendar.PARAM_SHORT_DESC, StringUtils.defaultString(cache.getShortDescription()),
-                    ICalendar.PARAM_START_TIME_MINUTES, StringUtils.defaultString(cache.guessEventTimeMinutes())
-                    );
-
-            activity.startActivity(new Intent(ICalendar.INTENT,
-                    Uri.parse(ICalendar.URI_SCHEME + "://" + ICalendar.URI_HOST + "?" + params.toString())));
-        } else {
-            // Inform user the calendar add-on is not installed and let them get it from Google Play
-            Dialogs.confirmYesNo(activity, R.string.addon_missing_title, new StringBuilder(res.getString(R.string.helper_calendar_missing))
-                    .append(' ')
-                    .append(res.getString(R.string.addon_download_prompt))
-                    .toString(), new DialogInterface.OnClickListener() {
-                @Override
-                public void onClick(DialogInterface dialog, int id) {
-                    final Intent intent = new Intent(Intent.ACTION_VIEW);
-                    intent.setData(Uri.parse(ICalendar.CALENDAR_ADDON_URI));
-                    activity.startActivity(intent);
-                }
-            });
-        }
-    }
-
 }

refactoring: move calendar addon code into a single class