<NME> SettingsActivity.java
<BEF> package cgeo.geocaching.settings;

import cgeo.geocaching.Intents;
import cgeo.geocaching.R;
import cgeo.geocaching.maps.mapsforge.v6.RenderThemeHelper;
import cgeo.geocaching.network.AndroidBeam;
import cgeo.geocaching.search.BaseSearchSuggestionCursor;
import cgeo.geocaching.search.BaseSuggestionsAdapter;
import cgeo.geocaching.search.SearchUtils;
import cgeo.geocaching.settings.fragments.BasePreferenceFragment;
import cgeo.geocaching.settings.fragments.PreferenceAppearanceFragment;
import cgeo.geocaching.settings.fragments.PreferenceBackupFragment;
import cgeo.geocaching.settings.fragments.PreferenceCachedetailsFragment;
import cgeo.geocaching.settings.fragments.PreferenceLoggingFragment;
import cgeo.geocaching.settings.fragments.PreferenceMapFragment;
import cgeo.geocaching.settings.fragments.PreferenceNavigationFragment;
import cgeo.geocaching.settings.fragments.PreferenceOfflinedataFragment;
import cgeo.geocaching.settings.fragments.PreferenceServiceGeocachingComFragment;
import cgeo.geocaching.settings.fragments.PreferenceServiceGeokretyOrgFragment;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceFragmentCompat;

import java.util.List;

/**
 * An {@link AppCompatActivity} that presents a set of application settings. On
 * handset devices, settings are presented as a single list. On tablets,
import android.view.View;
import android.widget.TextView;

import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
    private ContentStorageActivityHelper contentStorageHelper = null;
    private CharSequence title;

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        ApplicationSettings.setLocale(this);
public class SettingsActivity extends AppCompatActivity implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {

    private static final String TITLE_TAG = "preferencesActivityTitle";
    private static final String INTENT_OPEN_SCREEN = "OPEN_SCREEN";
    public static final int NO_RESTART_NEEDED = 1;
    public static final int RESTART_NEEDED = 2;

    public static final String STATE_CSAH = "csah";
    public static final String STATE_BACKUPUTILS = "backuputils";

    private BackupUtils backupUtils = null;
    private ContentStorageActivityHelper contentStorageHelper = null;
    private CharSequence title;

    private static final ArrayList<BasePreferenceFragment.PrefSearchDescriptor> searchIndex = new ArrayList<>();

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        ApplicationSettings.setLocale(this);
        setTheme(Settings.isLightSkin(this) ? R.style.settings_light : R.style.settings);
        super.onCreate(savedInstanceState);

        backupUtils = new BackupUtils(SettingsActivity.this, savedInstanceState == null ? null : savedInstanceState.getBundle(STATE_BACKUPUTILS));

        this.contentStorageHelper = new ContentStorageActivityHelper(this, savedInstanceState == null ? null : savedInstanceState.getBundle(STATE_CSAH))
            title = savedInstanceState.getCharSequence(TITLE_TAG);
        }
        getSupportFragmentManager().addOnBackStackChangedListener(() -> {
            if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
                setTitle(R.string.settings_titlebar);
            }
        });
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);


        setContentView(R.layout.layout_settings);
        setResult(NO_RESTART_NEEDED);
    }

    /**
     * This method sets the fragment which is used upon opening the settings. This may be the user directly or a
     * requesting Intent.
        } else {
            try {
                fragment = getString(fragmentId);
            } catch (Exception ignore) {
                fragment = "";
            }

        }
        openRequestedFragment(fragment, preference);
    }

    /**
     * This method sets the fragment given by its base key,
     * and optionally starts scrolling to a preference identified by its key
     */
    private void openRequestedFragment(@NonNull final String baseKey, @Nullable final String scrollToPrefKey) {
        Fragment preferenceFragment = new PreferencesFragment();
        if (StringUtils.equals(baseKey, getString(R.string.preference_screen_services))) {
            preferenceFragment = new PreferenceServicesFragment();
        } else if (StringUtils.equals(baseKey, getString(R.string.preference_screen_appearance))) {
            preferenceFragment = new PreferenceAppearanceFragment();
        } else if (StringUtils.equals(baseKey, getString(R.string.preference_screen_cachedetails))) {
            preferenceFragment = new PreferenceCachedetailsFragment();
        } else if (StringUtils.equals(baseKey, getString(R.string.preference_screen_map))) {
            preferenceFragment = new PreferenceMapFragment();
        } else if (StringUtils.equals(baseKey, getString(R.string.preference_screen_map))) {
            preferenceFragment = new PreferenceMapFragment();
        } else if (StringUtils.equals(baseKey, getString(R.string.preference_screen_logging))) {
            preferenceFragment = new PreferenceLoggingFragment();
        } else if (StringUtils.equals(baseKey, getString(R.string.preference_screen_offlinedata))) {
            preferenceFragment = new PreferenceOfflinedataFragment();
        } else if (StringUtils.equals(baseKey, getString(R.string.preference_screen_navigation))) {
            preferenceFragment = new PreferenceNavigationFragment();
        } else if (StringUtils.equals(baseKey, getString(R.string.preference_screen_system))) {
            preferenceFragment = new PreferenceSystemFragment();
        } else if (StringUtils.equals(baseKey, getString(R.string.preference_screen_backup))) {
            preferenceFragment = new PreferenceBackupFragment();

        } else if (StringUtils.equals(baseKey, getString(R.string.preference_screen_sendtocgeo))) {
            preferenceFragment = new PreferenceServiceSendToCgeoFragment();
        } else if (StringUtils.equals(baseKey, getString(R.string.preference_screen_geokrety))) {
            preferenceFragment = new PreferenceServiceGeokretyOrgFragment();
        } else if (StringUtils.equals(baseKey, getString(R.string.preference_screen_gc))) {
            preferenceFragment = new PreferenceServiceGeocachingComFragment();
        }
        if (StringUtils.isNotBlank(scrollToPrefKey)) {
            ((BasePreferenceFragment) preferenceFragment).setScrollToPrefCallback(this::scrollToCallback, baseKey, scrollToPrefKey);
        }
        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.settings_fragment_root, preferenceFragment)
                .commit();
    }

    @Override
    public void onSaveInstanceState(@NonNull final Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        savedInstanceState.putBundle(STATE_CSAH, contentStorageHelper.getState());
        savedInstanceState.putBundle(STATE_BACKUPUTILS, backupUtils.getState());

        // Save current activity title so we can set it again after a configuration change
        savedInstanceState.putCharSequence(TITLE_TAG, title);
    }

    @Override
    protected void onPause() {
        Log.i("Requesting settings backup with settings manager");
        BackupManager.dataChanged(getPackageName());
        super.onPause();
    }

    public static void openForScreen(final int preferenceScreenKey, final Context fromActivity) {
        final Intent intent = new Intent(fromActivity, SettingsActivity.class);
        intent.putExtra(INTENT_OPEN_SCREEN, preferenceScreenKey);
        fromActivity.startActivity(intent);
    }

    @Override
    public boolean onSupportNavigateUp() {
        if (getSupportFragmentManager().popBackStackImmediate()) {
            return true;
        }
        return super.onSupportNavigateUp();
    }


    @Override
    public boolean onPreferenceStartFragment(final PreferenceFragmentCompat caller, final Preference pref) {

    }

}

    public ContentStorageActivityHelper getCsah() {
        return contentStorageHelper;
    }

    @Override
    protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (contentStorageHelper.onActivityResult(requestCode, resultCode, data)) {
            return;
        }
        if (backupUtils.onActivityResult(requestCode, resultCode, data)) {
            return;
        }
    }

    // search related extensions

    private void buildSearchIndex() {
        synchronized (searchIndex) {
            if (searchIndex.size() > 0) {
                return;
            }
        }
        final ArrayList<BasePreferenceFragment> fragments = new ArrayList<>();
        fragments.add(new PreferenceServicesFragment().setIcon(R.drawable.settings_cloud));
        fragments.add(new PreferenceAppearanceFragment().setIcon(R.drawable.settings_eye));
        fragments.add(new PreferenceCachedetailsFragment().setIcon(R.drawable.settings_details));
        fragments.add(new PreferenceMapFragment().setIcon(R.drawable.settings_map));
        fragments.add(new PreferenceLoggingFragment().setIcon(R.drawable.settings_pen));
        fragments.add(new PreferenceOfflinedataFragment().setIcon(R.drawable.settings_sdcard));
        fragments.add(new PreferenceNavigationFragment().setIcon(R.drawable.settings_arrow));
        fragments.add(new PreferenceSystemFragment().setIcon(R.drawable.settings_nut));
        fragments.add(new PreferenceBackupFragment().setIcon(R.drawable.settings_backup));

        for (BasePreferenceFragment f : fragments) {
            f.setSearchdataCallback(this::collectSearchdataCallback);
        }
        final FragmentTransaction t = getSupportFragmentManager().beginTransaction();
        for (Fragment f : fragments) {
            t.add(R.id.settings_fragment_root, f);
        }
        t.commit();
        getSupportFragmentManager()
                .beginTransaction()
                .replace(R.id.settings_fragment_root, new PreferencesFragment())
                .commit();
    }

    // callback for BasePreferenceFragments to register search data
    private void collectSearchdataCallback(final ArrayList<BasePreferenceFragment.PrefSearchDescriptor> data) {
        synchronized (searchIndex) {
            searchIndex.addAll(data);
        }
    }

    // callback for BasePreferenceFragments for scrolling to a specific pref
    private void scrollToCallback(final String baseKey, final String prefKey) {
        for (Fragment fragment : getSupportFragmentManager().getFragments()) {
            final PreferenceScreen prefScreen = ((PreferenceFragmentCompat) fragment).getPreferenceScreen();
            if (prefScreen != null && StringUtils.equalsIgnoreCase(prefScreen.getKey(), baseKey)) {
                final Preference pref = prefScreen.findPreference(prefKey);
                if (pref != null) {
                    ((PreferenceFragmentCompat) fragment).scrollToPreference(pref);
                    pref.setTitle(prepareDisplayString(this, (String) pref.getTitle(), (String) pref.getTitle()));
                }
            }
        }
    }

    @Override
    public boolean onCreateOptionsMenu(final Menu menu) {
        getMenuInflater().inflate(R.menu.settings_activity_options, menu);

        // prepare search in action bar
        final SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        final MenuItem menuSearch = menu.findItem(R.id.menu_gosearch);
        final SearchView searchView = (SearchView) menuSearch.getActionView();
        searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
        searchView.setSuggestionsAdapter(new SettingsSuggestionsAdapter(this, searchIndex));

        SearchUtils.hideKeyboardOnSearchClick(searchView, menuSearch);
        SearchUtils.hideActionIconsWhenSearchIsActive(this, menu, menuSearch);
        SearchUtils.handleDropDownVisibility(this, searchView, menuSearch);

        return true;
    }

    static class SettingsSuggestionsAdapter extends BaseSuggestionsAdapter {
        private final ArrayList<BasePreferenceFragment.PrefSearchDescriptor> searchdata;

        SettingsSuggestionsAdapter(final SettingsActivity context, final ArrayList<BasePreferenceFragment.PrefSearchDescriptor> searchdata) {
            super(context, new SettingsSearchSuggestionCursor(), 0);
            this.searchdata = searchdata;
        }

        @Override
        public void bindView(final View view, final Context context, final Cursor cursor) {
            final TextView tv = view.findViewById(R.id.text);
            tv.setText(prepareDisplayString(context, cursor.getString(1), searchTerm));
            tv.setCompoundDrawablesWithIntrinsicBounds(cursor.getInt(5), 0, 0, 0);
            ((TextView) view.findViewById(R.id.info)).setText(prepareDisplayString(context, cursor.getString(2), searchTerm));
        }

        @Override
        protected Cursor query(@NonNull final String searchTerm) {
            final SettingsSearchSuggestionCursor resultCursor = new SettingsSearchSuggestionCursor();
            if (searchTerm.length() > 2) {
                synchronized (searchdata) {
                    for (BasePreferenceFragment.PrefSearchDescriptor item : searchdata) {
                        if (StringUtils.containsIgnoreCase(item.prefTitle, searchTerm) || StringUtils.containsIgnoreCase(item.prefSummary, searchTerm)) {
                            resultCursor.addItem(item.prefTitle, item.prefSummary, item.prefKey, item.prefCategoryIconRes);
                        }
                    }
                }
            }
            return resultCursor;
        }
    }

    static class SettingsSearchSuggestionCursor extends BaseSearchSuggestionCursor {
        public void addItem(@NonNull final CharSequence title, @NonNull final CharSequence summary, final String key, @DrawableRes final int iconRes) {
            addRow(new String[]{
                    String.valueOf(rowId),
                    (String) title,
                    (String) summary,
                    Intents.ACTION_SETTINGS,
                    key,
                    String.valueOf(iconRes)
            });
            rowId++;
        }
    }

    private static Spannable prepareDisplayString(final Context context, final String text, final String searchTerm) {
        if (StringUtils.isBlank(text)) {
            return null;
        }
        final int iPos = StringUtils.indexOfIgnoreCase(text, searchTerm);
        final Spannable s = new SpannableString(text);
        if (iPos >= 0) {
            s.setSpan(new BackgroundColorSpan(context.getResources().getColor(R.color.colorAccent)), iPos, iPos + searchTerm.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
            s.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.colorText)), iPos, iPos + searchTerm.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        return s;
    }

}

<MSG> enable search in preference screen (rel. to #12335)

<DFF> @@ -20,14 +20,24 @@ import android.app.backup.BackupManager;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.SearchView;
+import androidx.core.text.HtmlCompat;
 import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
 import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceGroup;
+import androidx.preference.PreferenceScreen;
 
 import java.util.List;
 
+import org.apache.commons.lang3.StringUtils;
+
 /**
  * An {@link AppCompatActivity} that presents a set of application settings. On
  * handset devices, settings are presented as a single list. On tablets,
@@ -52,6 +62,11 @@ public class SettingsActivity extends AppCompatActivity implements PreferenceFra
     private ContentStorageActivityHelper contentStorageHelper = null;
     private CharSequence title;
 
+    private SearchView searchView = null;
+    private MenuItem menuSearch = null;
+    private CharSequence lastTitle = null;
+    private Preference lastResult = null;
+
     @Override
     protected void onCreate(final Bundle savedInstanceState) {
         ApplicationSettings.setLocale(this);
@@ -83,9 +98,13 @@ public class SettingsActivity extends AppCompatActivity implements PreferenceFra
             title = savedInstanceState.getCharSequence(TITLE_TAG);
         }
         getSupportFragmentManager().addOnBackStackChangedListener(() -> {
-            if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+            final boolean isMainPreferenceScreen = getSupportFragmentManager().getBackStackEntryCount() == 0;
+            if (isMainPreferenceScreen) {
                 setTitle(R.string.settings_titlebar);
             }
+            if (menuSearch != null) {
+                menuSearch.setVisible(!isMainPreferenceScreen);
+            }
         });
         getSupportActionBar().setDisplayHomeAsUpEnabled(true);
 
@@ -94,6 +113,67 @@ public class SettingsActivity extends AppCompatActivity implements PreferenceFra
         setResult(NO_RESTART_NEEDED);
     }
 
+    @Override
+    public boolean onCreateOptionsMenu(final Menu menu) {
+        getMenuInflater().inflate(R.menu.settings_activity_options, menu);
+
+        // prepare search in action bar
+        menuSearch = menu.findItem(R.id.menu_gosearch);
+        searchView = (SearchView) menuSearch.getActionView();
+        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+            @Override
+            public boolean onQueryTextSubmit(final String s) {
+                return true;
+            }
+
+            @Override
+            public boolean onQueryTextChange(final String s) {
+                if (lastResult != null) {
+                    lastResult.setTitle(lastTitle);
+                }
+
+                final PreferenceFragmentCompat fragment = ((PreferenceFragmentCompat) getSupportFragmentManager().getFragments().get(0));
+                final Preference result = doSearch(s, ((PreferenceScreen) fragment.getPreferenceScreen()));
+                if (result != null) {
+                    fragment.scrollToPreference(result);
+                    if (s.length() > 1) {
+                        lastResult = result;
+                        lastTitle = result.getTitle();
+                        result.setTitle(HtmlCompat.fromHtml("<font color=#00eeee>" + lastTitle + "</font>", 0));
+                    }
+                }
+                return true;
+            }
+        });
+
+        return true;
+    }
+
+    /**
+     * searches recursively in all elements of given prefGroup for first occurrence of s
+     * returns found preference on success, null else
+     * (prefers preference entries over preference groups)
+     */
+    @Nullable
+    private Preference doSearch(final String s, final PreferenceGroup prefGroup) {
+        final int prefCount = prefGroup.getPreferenceCount();
+        for (int i = 0; i < prefCount; i++) {
+            final Preference pref = prefGroup.getPreference(i);
+            final CharSequence title = pref.getTitle();
+            final CharSequence summary = pref.getSummary();
+
+            if (pref instanceof PreferenceGroup) {
+                final Preference result = doSearch(s, (PreferenceGroup) pref);
+                if (result != null) {
+                    return result;
+                }
+            } else if (StringUtils.containsIgnoreCase(title, s) || StringUtils.containsIgnoreCase(summary, s)) {
+                return pref;
+            }
+        }
+        return null;
+    }
+
     /**
      * This method sets the fragment which is used upon opening the settings. This may be the user directly or a
      * requesting Intent.
@@ -188,4 +268,15 @@ public class SettingsActivity extends AppCompatActivity implements PreferenceFra
 
     }
 
+    @Override
+    public void onBackPressed() {
+        // back may exit the app instead of closing the search action bar
+        if (searchView != null && !searchView.isIconified()) {
+            searchView.setIconified(true);
+            menuSearch.collapseActionView();
+        } else {
+            super.onBackPressed();
+        }
+    }
+
 }

enable search in preference screen (rel. to #12335)