OmniControl: add settings search
based on SettingsIntelligence but without the fuzz
of having a DB. We have so little settings that we
can parse the xml files on the fly
Change-Id: Ifdced163c795c7a77a4b4128bd9b24f7a08aed8a
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..7e42cec
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="KotlinJpsPluginSettings">
+ <option name="version" value="1.8.0-release" />
+ </component>
+</project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 050a237..5d48e17 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
diff --git a/app/src/main/java/org/omnirom/control/AbstractSettingsFragment.kt b/app/src/main/java/org/omnirom/control/AbstractSettingsFragment.kt
index c806cc0..5ce7592 100644
--- a/app/src/main/java/org/omnirom/control/AbstractSettingsFragment.kt
+++ b/app/src/main/java/org/omnirom/control/AbstractSettingsFragment.kt
@@ -18,7 +18,10 @@
package org.omnirom.control
import android.os.Bundle
+import android.util.Log
import androidx.preference.PreferenceFragmentCompat
+import org.omnirom.control.search.PreferenceXmlParserUtils
+import org.omnirom.control.search.PreferenceXmlParserUtils.METADATA_KEY
import org.omnirom.omnilib.preference.SecureCheckBoxPreference
import org.omnirom.omnilib.preference.SecureSettingSwitchPreference
import org.omnirom.omnilib.preference.SystemCheckBoxPreference
diff --git a/app/src/main/java/org/omnirom/control/AppListFragment.kt b/app/src/main/java/org/omnirom/control/AppListFragment.kt
index c603d8f..117ae6d 100644
--- a/app/src/main/java/org/omnirom/control/AppListFragment.kt
+++ b/app/src/main/java/org/omnirom/control/AppListFragment.kt
@@ -20,6 +20,8 @@
import android.content.ComponentName
import android.content.Intent
import android.os.Bundle
+import androidx.annotation.DrawableRes
+import androidx.annotation.XmlRes
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
@@ -30,6 +32,12 @@
lateinit var appManager: ApplicationManager
var updateAppList: Boolean = false
+ companion object {
+ @DrawableRes
+ val ICON_RES = R.drawable.applist_icon
+ @XmlRes
+ val XML_RES = R.xml.applist_preferences
+ }
override fun getFragmentTitle(): String {
return resources.getString(R.string.applist_settings_title)
}
@@ -39,11 +47,11 @@
}
override fun getFragmentIcon(): Int {
- return R.drawable.applist_icon
+ return ICON_RES
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- setPreferencesFromResource(R.xml.applist_preferences, rootKey)
+ setPreferencesFromResource(XML_RES, rootKey)
appManager = ApplicationManager(requireContext())
diff --git a/app/src/main/java/org/omnirom/control/BarsSettingsFragment.kt b/app/src/main/java/org/omnirom/control/BarsSettingsFragment.kt
index c69ba4d..8fd18ac 100644
--- a/app/src/main/java/org/omnirom/control/BarsSettingsFragment.kt
+++ b/app/src/main/java/org/omnirom/control/BarsSettingsFragment.kt
@@ -20,16 +20,24 @@
import android.graphics.Rect
import android.os.Bundle
import android.util.DisplayMetrics
+import android.util.Log
import android.view.WindowManager
+import androidx.annotation.DrawableRes
+import androidx.annotation.XmlRes
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
+import org.omnirom.control.search.PreferenceXmlParserUtils
+import org.omnirom.control.search.PreferenceXmlParserUtils.METADATA_KEY
import kotlin.math.min
class BarsSettingsFragment : AbstractSettingsFragment() {
-
private val TABLET_MIN_DPS = 600
+ companion object {
+ @DrawableRes val ICON_RES = R.drawable.ic_bars_tile
+ @XmlRes val XML_RES = R.xml.bars_settings_preferences
+ }
override fun getFragmentTitle(): String {
return resources.getString(R.string.bars_settings_title)
}
@@ -39,11 +47,11 @@
}
override fun getFragmentIcon(): Int {
- return R.drawable.ic_bars_tile
+ return ICON_RES
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- setPreferencesFromResource(R.xml.bars_settings_preferences, rootKey)
+ setPreferencesFromResource(XML_RES, rootKey)
val taskbarCategory:PreferenceCategory? = findPreference("category_taskbar")
if (taskbarCategory != null){
@@ -53,10 +61,6 @@
}
}
- override fun onPreferenceTreeClick(preference: Preference): Boolean {
- return super.onPreferenceTreeClick(preference)
- }
-
private fun dpiFromPx(size: Int, densityDpi: Int): Float {
val densityRatio = densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT
return size / densityRatio
diff --git a/app/src/main/java/org/omnirom/control/BatteryLightSettingsFragment.java b/app/src/main/java/org/omnirom/control/BatteryLightSettingsFragment.java
index 962487a..a67d5c3 100644
--- a/app/src/main/java/org/omnirom/control/BatteryLightSettingsFragment.java
+++ b/app/src/main/java/org/omnirom/control/BatteryLightSettingsFragment.java
@@ -23,6 +23,9 @@
import android.os.Bundle;
import android.os.UserHandle;
import android.provider.Settings;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.XmlRes;
import androidx.preference.Preference;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
@@ -60,6 +63,13 @@
private SystemSettingSwitchPreference mOnlyFullPref;
private SystemSettingSwitchPreference mFastBatteryLightEnabledPref;
+ public static final @DrawableRes int ICON_RES = R.drawable.ic_settings_leds;
+ public static final @XmlRes int XML_RES = R.xml.battery_light_settings_preferences;
+
+ public static boolean isEnabled(Context context) {
+ return context.getResources().getBoolean(com.android.internal.R.bool.config_intrusiveBatteryLed);
+ }
+
@Override
public String getFragmentTitle() {
return getString(R.string.batterylight_title);
@@ -72,12 +82,12 @@
@Override
public int getFragmentIcon() {
- return R.drawable.ic_settings_leds;
+ return ICON_RES;
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
- setPreferencesFromResource(R.xml.battery_light_settings_preferences, rootKey);
+ setPreferencesFromResource(XML_RES, rootKey);
PreferenceScreen prefSet = getPreferenceScreen();
ContentResolver resolver = getContext().getContentResolver();
diff --git a/app/src/main/java/org/omnirom/control/ButtonSettingsFragment.kt b/app/src/main/java/org/omnirom/control/ButtonSettingsFragment.kt
index ba60c30..da0f713 100644
--- a/app/src/main/java/org/omnirom/control/ButtonSettingsFragment.kt
+++ b/app/src/main/java/org/omnirom/control/ButtonSettingsFragment.kt
@@ -20,6 +20,8 @@
import android.content.res.Resources
import android.os.Bundle
import android.provider.Settings
+import androidx.annotation.DrawableRes
+import androidx.annotation.XmlRes
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
@@ -32,6 +34,13 @@
private val KEY_ADVANCED_REBOOT = "advanced_reboot"
private val KEY_POWER_TORCH = "long_press_power_torch"
+ companion object {
+ @DrawableRes
+ val ICON_RES = R.drawable.ic_settings_buttons
+ @XmlRes
+ val XML_RES = R.xml.button_settings_preferences
+ }
+
override fun getFragmentTitle(): String {
return resources.getString(R.string.button_settings_title)
}
@@ -41,11 +50,11 @@
}
override fun getFragmentIcon(): Int {
- return R.drawable.ic_settings_buttons
+ return ICON_RES
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- setPreferencesFromResource(R.xml.button_settings_preferences, rootKey)
+ setPreferencesFromResource(XML_RES, rootKey)
val powerCategory: PreferenceCategory? = findPreference(CATEGORY_POWER)
if (powerCategory != null) {
diff --git a/app/src/main/java/org/omnirom/control/DialerSettingsFragment.kt b/app/src/main/java/org/omnirom/control/DialerSettingsFragment.kt
index 5762e1a..159f452 100644
--- a/app/src/main/java/org/omnirom/control/DialerSettingsFragment.kt
+++ b/app/src/main/java/org/omnirom/control/DialerSettingsFragment.kt
@@ -17,14 +17,28 @@
*/
package org.omnirom.control
+import android.content.Context
import android.os.Bundle
import android.provider.Settings
+import androidx.annotation.DrawableRes
+import androidx.annotation.XmlRes
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
+import org.omnirom.omnilibcore.utils.DeviceUtils
class DialerSettingsFragment : AbstractSettingsFragment() {
+ companion object {
+ @DrawableRes
+ val ICON_RES = R.drawable.ic_dialer_tile
+
+ @XmlRes
+ val XML_RES = R.xml.dialer_settings_preferences
+ fun isEnabled(context: Context): Boolean {
+ return DeviceUtils.isVoiceCapable(context)
+ }
+ }
override fun getFragmentTitle(): String {
return resources.getString(R.string.dialer_settings_title)
@@ -35,11 +49,11 @@
}
override fun getFragmentIcon(): Int {
- return R.drawable.ic_dialer_tile
+ return ICON_RES
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- setPreferencesFromResource(R.xml.dialer_settings_preferences, rootKey)
+ setPreferencesFromResource(XML_RES, rootKey)
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
diff --git a/app/src/main/java/org/omnirom/control/FingerprintSettingsFragment.kt b/app/src/main/java/org/omnirom/control/FingerprintSettingsFragment.kt
index ab573d0..0245d50 100644
--- a/app/src/main/java/org/omnirom/control/FingerprintSettingsFragment.kt
+++ b/app/src/main/java/org/omnirom/control/FingerprintSettingsFragment.kt
@@ -36,6 +36,8 @@
import android.util.Log
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.annotation.DrawableRes
+import androidx.annotation.XmlRes
import androidx.preference.Preference
import androidx.preference.SwitchPreference
import com.android.systemui.shared.omni.IOmniSystemUiProxy
@@ -49,6 +51,17 @@
private val FINGERPRINT_CUSTOM_ICON_ENABLE = "custom_fingerprint_icon_enable"
private val UFPSIMAGE_FILE_NAME = "ufpsImage"
+ companion object {
+ @DrawableRes
+ val ICON_RES = R.drawable.ic_settings_fingerprint
+ @XmlRes
+ val XML_RES = R.xml.fingerprint_preferences
+
+ fun isEnabled(context: Context) : Boolean {
+ return context.resources.getBoolean(R.bool.config_has_udfps)
+ }
+ }
+
class PickSinglePhotoContract : ActivityResultContract<Void?, Uri?>() {
override fun createIntent(context: Context, input: Void?): Intent {
return Intent(Intent(MediaStore.ACTION_PICK_IMAGES)).apply { type = "image/*" }
@@ -141,7 +154,7 @@
}
override fun getFragmentIcon(): Int {
- return R.drawable.ic_settings_fingerprint
+ return ICON_RES
}
override fun onDestroy() {
@@ -150,7 +163,7 @@
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- setPreferencesFromResource(R.xml.fingerprint_preferences, rootKey)
+ setPreferencesFromResource(XML_RES, rootKey)
mEnableCustom = findPreference<SwitchPreference>(FINGERPRINT_CUSTOM_ICON_ENABLE)
mEnableCustom?.let {
diff --git a/app/src/main/java/org/omnirom/control/GridViewFragment.kt b/app/src/main/java/org/omnirom/control/GridViewFragment.kt
index 81f95e4..df30d71 100644
--- a/app/src/main/java/org/omnirom/control/GridViewFragment.kt
+++ b/app/src/main/java/org/omnirom/control/GridViewFragment.kt
@@ -28,6 +28,7 @@
import androidx.fragment.app.Fragment
import com.android.internal.util.ArrayUtils;
+import org.omnirom.control.search.SearchFragment
import org.omnirom.omnilibcore.utils.DeviceUtils
@@ -80,6 +81,14 @@
gridItems.clear()
gridItems.add(
FragmentGridItem(
+ R.string.search_fragment_title,
+ R.string.search_fragment_summary,
+ R.drawable.ic_search,
+ SearchFragment()
+ )
+ )
+ gridItems.add(
+ FragmentGridItem(
R.string.applist_settings_title,
R.string.applist_settings_summary,
R.drawable.applist_icon,
@@ -331,7 +340,7 @@
if (gridItem is FragmentGridItem)
requireActivity().supportFragmentManager
.beginTransaction()
- .replace(R.id.settings, gridItem.gridFragment)
+ .replace(R.id.settings, gridItem.gridFragment.javaClass, null)
.addToBackStack(null)
.commit()
else if (gridItem is IntentGridItem)
diff --git a/app/src/main/java/org/omnirom/control/LockscreenSettingsFragment.kt b/app/src/main/java/org/omnirom/control/LockscreenSettingsFragment.kt
index 97928d1..006da54 100644
--- a/app/src/main/java/org/omnirom/control/LockscreenSettingsFragment.kt
+++ b/app/src/main/java/org/omnirom/control/LockscreenSettingsFragment.kt
@@ -18,11 +18,18 @@
package org.omnirom.control
import android.os.Bundle
+import androidx.annotation.DrawableRes
+import androidx.annotation.XmlRes
import androidx.preference.Preference
class LockscreenSettingsFragment : AbstractSettingsFragment() {
-
+ companion object {
+ @DrawableRes
+ val ICON_RES = R.drawable.ic_lockscreen_tile
+ @XmlRes
+ val XML_RES = R.xml.lockscreen_settings_preferences
+ }
override fun getFragmentTitle(): String {
return resources.getString(R.string.lockscreen_item_title)
}
@@ -32,11 +39,11 @@
}
override fun getFragmentIcon(): Int {
- return R.drawable.ic_lockscreen_tile
+ return ICON_RES
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- setPreferencesFromResource(R.xml.lockscreen_settings_preferences, rootKey)
+ setPreferencesFromResource(XML_RES, rootKey)
val aodOnCharging: Preference? = findPreference("doze_on_charge")
if (aodOnCharging != null) {
diff --git a/app/src/main/java/org/omnirom/control/MoreSettingsFragment.kt b/app/src/main/java/org/omnirom/control/MoreSettingsFragment.kt
index 7c6b2e8..e05b28f 100644
--- a/app/src/main/java/org/omnirom/control/MoreSettingsFragment.kt
+++ b/app/src/main/java/org/omnirom/control/MoreSettingsFragment.kt
@@ -18,11 +18,18 @@
package org.omnirom.control
import android.os.Bundle
+import androidx.annotation.DrawableRes
+import androidx.annotation.XmlRes
import androidx.preference.Preference
class MoreSettingsFragment : AbstractSettingsFragment() {
-
+ companion object {
+ @DrawableRes
+ val ICON_RES = R.drawable.ic_settings_more
+ @XmlRes
+ val XML_RES = R.xml.more_settings_preferences
+ }
override fun getFragmentTitle(): String {
return resources.getString(R.string.more_settings_title)
}
@@ -32,11 +39,11 @@
}
override fun getFragmentIcon(): Int {
- return R.drawable.ic_settings_more
+ return ICON_RES
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- setPreferencesFromResource(R.xml.more_settings_preferences, rootKey)
+ setPreferencesFromResource(XML_RES, rootKey)
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
diff --git a/app/src/main/java/org/omnirom/control/QSSettingsFragment.kt b/app/src/main/java/org/omnirom/control/QSSettingsFragment.kt
index 8a9e678..79088e1 100644
--- a/app/src/main/java/org/omnirom/control/QSSettingsFragment.kt
+++ b/app/src/main/java/org/omnirom/control/QSSettingsFragment.kt
@@ -19,12 +19,19 @@
import android.os.Bundle
import android.provider.Settings
+import androidx.annotation.DrawableRes
+import androidx.annotation.XmlRes
import androidx.preference.Preference
import org.omnirom.omnilib.preference.SeekBarPreference
class QSSettingsFragment : AbstractSettingsFragment() {
-
+ companion object {
+ @DrawableRes
+ val ICON_RES = R.drawable.ic_qs_tile
+ @XmlRes
+ val XML_RES = R.xml.qs_settings_preferences
+ }
override fun getFragmentTitle(): String {
return resources.getString(R.string.qs_settings_title)
}
@@ -34,11 +41,11 @@
}
override fun getFragmentIcon(): Int {
- return R.drawable.ic_qs_tile
+ return ICON_RES
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- setPreferencesFromResource(R.xml.qs_settings_preferences, rootKey)
+ setPreferencesFromResource(XML_RES, rootKey)
val pm = requireContext().packageManager
val resources = pm.getResourcesForApplication("com.android.systemui")
diff --git a/app/src/main/java/org/omnirom/control/SettingsActivity.kt b/app/src/main/java/org/omnirom/control/SettingsActivity.kt
index 0758d52..7405e7d 100644
--- a/app/src/main/java/org/omnirom/control/SettingsActivity.kt
+++ b/app/src/main/java/org/omnirom/control/SettingsActivity.kt
@@ -29,6 +29,7 @@
import androidx.appcompat.widget.Toolbar
import androidx.core.view.WindowCompat
import androidx.fragment.app.Fragment
+import org.omnirom.control.search.SearchProvider
class SettingsActivity : AppCompatActivity() {
@@ -60,7 +61,7 @@
fragment = savedFragment
}
supportFragmentManager.beginTransaction()
- .replace(R.id.settings, fragment)
+ .replace(R.id.settings, fragment.javaClass, null)
.commit()
val toolbar: Toolbar = findViewById(R.id.toolbar)
diff --git a/app/src/main/java/org/omnirom/control/search/PreferenceXmlParserUtils.java b/app/src/main/java/org/omnirom/control/search/PreferenceXmlParserUtils.java
new file mode 100644
index 0000000..82e48fe
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/search/PreferenceXmlParserUtils.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+package org.omnirom.control.search;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.XmlRes;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.VisibleForTesting;
+
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Utility class to parse elements of XML preferences
+ */
+public class PreferenceXmlParserUtils {
+
+ private static final String TAG = "PreferenceXmlParserUtil";
+ @VisibleForTesting
+ static final String PREF_SCREEN_TAG = "PreferenceScreen";
+ private static final List<String> SUPPORTED_PREF_TYPES = Arrays.asList(
+ "Preference");
+ /**
+ * Flag definition to indicate which metadata should be extracted when
+ * {@link #extractMetadata(Context, int, int)} is called. The flags can be combined by using |
+ * (binary or).
+ */
+ @IntDef(flag = true, value = {
+ MetadataFlag.FLAG_INCLUDE_PREF_SCREEN,
+ MetadataFlag.FLAG_NEED_KEY,
+ MetadataFlag.FLAG_NEED_PREF_TYPE,
+ MetadataFlag.FLAG_NEED_PREF_TITLE,
+ MetadataFlag.FLAG_NEED_PREF_SUMMARY,
+ MetadataFlag.FLAG_NEED_PREF_ICON})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface MetadataFlag {
+
+ int FLAG_INCLUDE_PREF_SCREEN = 1;
+ int FLAG_NEED_KEY = 1 << 1;
+ int FLAG_NEED_PREF_TYPE = 1 << 2;
+ int FLAG_NEED_PREF_TITLE = 1 << 3;
+ int FLAG_NEED_PREF_SUMMARY = 1 << 4;
+ int FLAG_NEED_PREF_ICON = 1 << 5;
+ }
+
+ public static final String METADATA_PREF_TYPE = "type";
+ public static final String METADATA_KEY = "key";
+ public static final String METADATA_TITLE = "title";
+ public static final String METADATA_SUMMARY = "summary";
+ public static final String METADATA_ICON = "icon";
+
+ private static final String ENTRIES_SEPARATOR = "|";
+
+ /**
+ * Call {@link #extractMetadata(Context, int, int)} with {@link #METADATA_KEY} instead.
+ */
+ @Deprecated
+ public static String getDataKey(Context context, AttributeSet attrs) {
+ return getStringData(context, attrs,
+ com.android.internal.R.styleable.Preference,
+ com.android.internal.R.styleable.Preference_key);
+ }
+
+ /**
+ * Call {@link #extractMetadata(Context, int, int)} with {@link #METADATA_TITLE} instead.
+ */
+ @Deprecated
+ public static String getDataTitle(Context context, AttributeSet attrs) {
+ return getStringData(context, attrs,
+ com.android.internal.R.styleable.Preference,
+ com.android.internal.R.styleable.Preference_title);
+ }
+
+ /**
+ * Call {@link #extractMetadata(Context, int, int)} with {@link #METADATA_SUMMARY} instead.
+ */
+ @Deprecated
+ public static String getDataSummary(Context context, AttributeSet attrs) {
+ return getStringData(context, attrs,
+ com.android.internal.R.styleable.Preference,
+ com.android.internal.R.styleable.Preference_summary);
+ }
+
+ /**
+ * Extracts metadata from preference xml and put them into a {@link Bundle}.
+ *
+ * @param xmlResId xml res id of a preference screen
+ * @param flags Should be one or more of {@link MetadataFlag}.
+ */
+ @NonNull
+ public static List<Bundle> extractMetadata(Context context, @XmlRes int xmlResId, int flags)
+ throws IOException, XmlPullParserException {
+ final List<Bundle> metadata = new ArrayList<>();
+ if (xmlResId <= 0) {
+ Log.d(TAG, xmlResId + " is invalid.");
+ return metadata;
+ }
+ final XmlResourceParser parser = context.getResources().getXml(xmlResId);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && type != XmlPullParser.START_TAG) {
+ // Parse next until start tag is found
+ }
+ final int outerDepth = parser.getDepth();
+ final boolean hasPrefScreenFlag = hasFlag(flags, MetadataFlag.FLAG_INCLUDE_PREF_SCREEN);
+ do {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ final String nodeName = parser.getName();
+ if (!hasPrefScreenFlag && TextUtils.equals(PREF_SCREEN_TAG, nodeName)) {
+ continue;
+ }
+ if (!SUPPORTED_PREF_TYPES.contains(nodeName) && !nodeName.endsWith("Preference")) {
+ continue;
+ }
+
+ final Bundle preferenceMetadata = new Bundle();
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ final TypedArray preferenceAttributes = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.Preference);
+ TypedArray preferenceScreenAttributes = null;
+ if (hasPrefScreenFlag) {
+ preferenceScreenAttributes = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.PreferenceScreen);
+ }
+
+ if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_TYPE)) {
+ preferenceMetadata.putString(METADATA_PREF_TYPE, nodeName);
+ }
+ if (hasFlag(flags, MetadataFlag.FLAG_NEED_KEY)) {
+ preferenceMetadata.putString(METADATA_KEY, getKey(preferenceAttributes));
+ }
+ if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_TITLE)) {
+ preferenceMetadata.putString(METADATA_TITLE, getTitle(preferenceAttributes));
+ }
+ if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_SUMMARY)) {
+ preferenceMetadata.putString(METADATA_SUMMARY, getSummary(preferenceAttributes));
+ }
+ if (hasFlag(flags, MetadataFlag.FLAG_NEED_PREF_ICON)) {
+ preferenceMetadata.putInt(METADATA_ICON, getIcon(preferenceAttributes));
+ }
+ Log.d(TAG, "preferenceMetadata = " + preferenceMetadata);
+
+ metadata.add(preferenceMetadata);
+
+ preferenceAttributes.recycle();
+ } while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth));
+ parser.close();
+ return metadata;
+ }
+
+ /**
+ * Call {@link #extractMetadata(Context, int, int)} with a {@link MetadataFlag} instead.
+ */
+ @Deprecated
+ @Nullable
+ private static String getStringData(Context context, AttributeSet set, int[] attrs, int resId) {
+ final TypedArray ta = context.obtainStyledAttributes(set, attrs);
+ String data = ta.getString(resId);
+ ta.recycle();
+ return data;
+ }
+
+ private static boolean hasFlag(int flags, @MetadataFlag int flag) {
+ return (flags & flag) != 0;
+ }
+
+ private static String getDataEntries(Context context, AttributeSet set, int[] attrs,
+ int resId) {
+ final TypedArray sa = context.obtainStyledAttributes(set, attrs);
+ final TypedValue tv = sa.peekValue(resId);
+ sa.recycle();
+ String[] data = null;
+ if (tv != null && tv.type == TypedValue.TYPE_REFERENCE) {
+ if (tv.resourceId != 0) {
+ data = context.getResources().getStringArray(tv.resourceId);
+ }
+ }
+ final int count = (data == null) ? 0 : data.length;
+ if (count == 0) {
+ return null;
+ }
+ final StringBuilder result = new StringBuilder();
+ for (int n = 0; n < count; n++) {
+ result.append(data[n]);
+ result.append(ENTRIES_SEPARATOR);
+ }
+ return result.toString();
+ }
+
+ private static String getKey(TypedArray styledAttributes) {
+ return styledAttributes.getString(com.android.internal.R.styleable.Preference_key);
+ }
+
+ private static String getTitle(TypedArray styledAttributes) {
+ return styledAttributes.getString(com.android.internal.R.styleable.Preference_title);
+ }
+
+ private static String getSummary(TypedArray styledAttributes) {
+ return styledAttributes.getString(com.android.internal.R.styleable.Preference_summary);
+ }
+
+ private static int getIcon(TypedArray styledAttributes) {
+ return styledAttributes.getResourceId(com.android.internal.R.styleable.Icon_icon, 0);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/omnirom/control/search/SearchFragment.kt b/app/src/main/java/org/omnirom/control/search/SearchFragment.kt
new file mode 100644
index 0000000..dd2573e
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/search/SearchFragment.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 The OmniROM Project
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.omnirom.control.search
+
+import android.content.Context
+import android.os.Bundle
+import android.text.Editable
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.TextWatcher
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.EditText
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import org.omnirom.control.R
+import org.omnirom.control.SettingsActivity
+import org.omnirom.control.widget.TintedDrawableSpan
+
+class SearchFragment : Fragment() {
+ private val listItems = mutableListOf<SearchResult>()
+ private lateinit var adapter: SearchResultAdapter
+
+ fun getFragmentTitle(): String {
+ return resources.getString(R.string.search_fragment_title)
+ }
+
+ fun getFragmentSummary(): String {
+ return resources.getString(R.string.search_fragment_summary)
+ }
+
+ fun getFragmentIcon(): Int {
+ return R.drawable.ic_search
+ }
+
+ override fun onResume() {
+ super.onResume()
+ (activity as SettingsActivity).let {
+ it.showToolbar()
+ it.updateFragmentTitle(
+ getFragmentTitle(),
+ "",
+ getFragmentIcon(),
+ false
+ )
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ return inflater.inflate(R.layout.search_fragment, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ val resultList: RecyclerView = view.findViewById(R.id.search_result_list)
+ adapter = SearchResultAdapter(listItems, (activity as SettingsActivity))
+ resultList.adapter = adapter
+ resultList.layoutManager = LinearLayoutManager(context)
+
+ val searchText: EditText = view.findViewById(R.id.search_result_string)
+ searchText.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
+ override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+ listItems.clear()
+ if (s.isNotEmpty()) {
+ SearchProvider.getInstance().getSearchResult(requireContext(), s, listItems)
+ }
+ adapter.notifyDataSetChanged()
+ }
+
+ override fun afterTextChanged(s: Editable) {}
+ })
+ }
+
+ private fun prefixTextWithIcon(
+ context: Context?,
+ iconRes: Int,
+ msg: CharSequence
+ ): CharSequence? {
+ val spanned = SpannableString(" $msg")
+ spanned.setSpan(
+ TintedDrawableSpan(context, iconRes),
+ 0, 1, Spannable.SPAN_EXCLUSIVE_INCLUSIVE
+ )
+ return spanned
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/omnirom/control/search/SearchItem.kt b/app/src/main/java/org/omnirom/control/search/SearchItem.kt
new file mode 100644
index 0000000..a2b3200
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/search/SearchItem.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The OmniROM Project
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.omnirom.control.search
+
+import android.content.Context
+import androidx.annotation.DrawableRes
+import androidx.annotation.XmlRes
+
+data class SearchItem(
+ @XmlRes val xmlResId: Int,
+ val className: String,
+ @DrawableRes val iconResId: Int,
+ val isEnabled: (context: Context) -> Boolean
+)
diff --git a/app/src/main/java/org/omnirom/control/search/SearchProvider.kt b/app/src/main/java/org/omnirom/control/search/SearchProvider.kt
new file mode 100644
index 0000000..1ddf97a
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/search/SearchProvider.kt
@@ -0,0 +1,313 @@
+/*
+ * Copyright (C) 2023 The OmniROM Project
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.omnirom.control.search
+
+import android.content.Context
+import android.util.Log
+import androidx.annotation.DrawableRes
+import androidx.annotation.XmlRes
+import org.omnirom.control.AppListFragment
+import org.omnirom.control.BarsSettingsFragment
+import org.omnirom.control.BatteryLightSettingsFragment
+import org.omnirom.control.ButtonSettingsFragment
+import org.omnirom.control.DialerSettingsFragment
+import org.omnirom.control.FingerprintSettingsFragment
+import org.omnirom.control.LockscreenSettingsFragment
+import org.omnirom.control.MoreSettingsFragment
+import org.omnirom.control.QSSettingsFragment
+import org.omnirom.control.search.PreferenceXmlParserUtils.METADATA_KEY
+import org.omnirom.control.search.PreferenceXmlParserUtils.METADATA_SUMMARY
+import org.omnirom.control.search.PreferenceXmlParserUtils.METADATA_TITLE
+import org.omnirom.control.search.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_KEY
+import org.omnirom.control.search.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_PREF_SUMMARY
+import org.omnirom.control.search.PreferenceXmlParserUtils.MetadataFlag.FLAG_NEED_PREF_TITLE
+import org.xmlpull.v1.XmlPullParserException
+import java.io.IOException
+
+
+class SearchProvider private constructor() {
+ private val TAG = "SearchProvider"
+
+ companion object {
+ val NO_SEARCH_INDEX = 0
+
+ private var instance: SearchProvider? = null
+
+ fun getInstance(): SearchProvider {
+ if (instance == null) {
+ instance = SearchProvider()
+ }
+ return instance ?: SearchProvider()
+ }
+ }
+
+ private val searchItemList = mutableListOf<SearchItem>()
+ private val searchResultCache = mutableListOf<SearchResult>()
+
+ private fun isEnabled(context: Context): Boolean {
+ return true
+ }
+
+ init {
+ searchItemList.add(
+ SearchItem(
+ BarsSettingsFragment.XML_RES,
+ BarsSettingsFragment().javaClass.canonicalName,
+ BarsSettingsFragment.ICON_RES,
+ this::isEnabled
+ )
+ )
+ searchItemList.add(
+ SearchItem(
+ MoreSettingsFragment.XML_RES,
+ MoreSettingsFragment().javaClass.canonicalName,
+ MoreSettingsFragment.ICON_RES,
+ this::isEnabled
+ )
+ )
+ searchItemList.add(
+ SearchItem(
+ LockscreenSettingsFragment.XML_RES,
+ LockscreenSettingsFragment().javaClass.canonicalName,
+ LockscreenSettingsFragment.ICON_RES,
+ this::isEnabled
+ )
+ )
+ searchItemList.add(
+ SearchItem(
+ QSSettingsFragment.XML_RES,
+ QSSettingsFragment().javaClass.canonicalName,
+ QSSettingsFragment.ICON_RES,
+ this::isEnabled
+ )
+ )
+
+ searchItemList.add(
+ SearchItem(
+ FingerprintSettingsFragment.XML_RES,
+ FingerprintSettingsFragment().javaClass.canonicalName,
+ FingerprintSettingsFragment.ICON_RES,
+ FingerprintSettingsFragment::isEnabled
+ )
+ )
+
+ searchItemList.add(
+ SearchItem(
+ DialerSettingsFragment.XML_RES,
+ DialerSettingsFragment().javaClass.canonicalName,
+ DialerSettingsFragment.ICON_RES,
+ DialerSettingsFragment::isEnabled
+ )
+ )
+
+ searchItemList.add(
+ SearchItem(
+ ButtonSettingsFragment.XML_RES,
+ ButtonSettingsFragment().javaClass.canonicalName,
+ ButtonSettingsFragment.ICON_RES,
+ this::isEnabled
+ )
+ )
+
+ searchItemList.add(
+ SearchItem(
+ BatteryLightSettingsFragment.XML_RES,
+ BatteryLightSettingsFragment().javaClass.canonicalName,
+ BatteryLightSettingsFragment.ICON_RES,
+ BatteryLightSettingsFragment::isEnabled
+ )
+ )
+
+ searchItemList.add(
+ SearchItem(
+ AppListFragment.XML_RES,
+ AppListFragment().javaClass.canonicalName,
+ AppListFragment.ICON_RES,
+ this::isEnabled
+ )
+ )
+ }
+
+ private fun getKeysFromXml(
+ context: Context, @XmlRes xmlResId: Int
+ ): List<String?>? {
+ val keys: MutableList<String?> = ArrayList()
+ try {
+ val metadata = PreferenceXmlParserUtils.extractMetadata(
+ context,
+ xmlResId, FLAG_NEED_KEY
+ )
+ for (bundle in metadata) {
+ keys.add(bundle.getString(METADATA_KEY))
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Error parsing non-indexable from xml $xmlResId")
+ } catch (e: XmlPullParserException) {
+ Log.w(TAG, "Error parsing non-indexable from xml $xmlResId")
+ }
+ return keys
+ }
+
+ private fun buildSearchResultsCache(context: Context) {
+ searchResultCache.clear()
+ searchItemList.forEach {
+ val xml = it.xmlResId
+ val className = it.className
+ if (xml != NO_SEARCH_INDEX) {
+ if (it.isEnabled(context)) {
+ getAllSearchResultsFromXml(
+ context,
+ xml,
+ className,
+ it.iconResId,
+ searchResultCache
+ )
+ }
+ }
+ }
+ }
+
+ private fun getAllSearchResultsFromXml(
+ context: Context,
+ @XmlRes xmlResId: Int,
+ className: String,
+ @DrawableRes iconResId: Int,
+ items: MutableList<SearchResult>
+ ) {
+ try {
+ val metadata = PreferenceXmlParserUtils.extractMetadata(
+ context,
+ xmlResId, FLAG_NEED_KEY or FLAG_NEED_PREF_TITLE or FLAG_NEED_PREF_SUMMARY
+ )
+ for (bundle in metadata) {
+ val title = bundle.getString(METADATA_TITLE)
+ val summary = bundle.getString(METADATA_SUMMARY)
+ val key = bundle.getString(METADATA_KEY)
+
+ if (title?.isNotEmpty() == true && key?.isNotEmpty() == true) {
+ items.add(
+ SearchResult(
+ title,
+ summary,
+ iconResId,
+ className,
+ key
+ )
+ )
+ }
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Error parsing non-indexable from xml $xmlResId")
+ } catch (e: XmlPullParserException) {
+ Log.w(TAG, "Error parsing non-indexable from xml $xmlResId")
+ }
+ }
+
+ private fun getMatchingStringsFromXml(
+ context: Context,
+ @XmlRes xmlResId: Int,
+ className: String,
+ @DrawableRes iconResId: Int,
+ searchString: String,
+ matchingItems: MutableList<SearchResult>
+ ) {
+ try {
+ val metadata = PreferenceXmlParserUtils.extractMetadata(
+ context,
+ xmlResId, FLAG_NEED_KEY or FLAG_NEED_PREF_TITLE or FLAG_NEED_PREF_SUMMARY
+ )
+ for (bundle in metadata) {
+ val title = bundle.getString(METADATA_TITLE)
+ val summary = bundle.getString(METADATA_SUMMARY)
+ val key = bundle.getString(METADATA_KEY)
+
+ if (title?.isNotEmpty() == true && key?.isNotEmpty() == true) {
+ if (title.contains(
+ searchString,
+ ignoreCase = true
+ ) || summary?.contains(searchString, ignoreCase = true) == true
+ ) {
+ matchingItems.add(
+ SearchResult(
+ title,
+ summary,
+ iconResId,
+ className,
+ key
+ )
+ )
+ continue
+ }
+ }
+ }
+ } catch (e: IOException) {
+ Log.w(TAG, "Error parsing non-indexable from xml $xmlResId")
+ } catch (e: XmlPullParserException) {
+ Log.w(TAG, "Error parsing non-indexable from xml $xmlResId")
+ }
+ }
+
+ fun getSearchResult(
+ context: Context,
+ searchString: CharSequence,
+ result: MutableList<SearchResult>
+ ) {
+ if (searchString.isNotEmpty()) {
+ // TODO maybe persist cache at some point but its so fast right now its not worth the effort
+ if (searchResultCache.isEmpty()) {
+ buildSearchResultsCache(context)
+ }
+ getSearchResultFromCache(searchString, result)
+ /*settingsFragment.forEach {
+ val xml = it.xmlResId
+ val className = it.className
+ if (xml != NO_SEARCH_INDEX) {
+ if (it.isEnabled(context)) {
+ getMatchingStringsFromXml(
+ context,
+ xml,
+ className,
+ it.iconResId,
+ searchString.toString(),
+ result
+ )
+ }
+ }
+ }*/
+ }
+ }
+
+ private fun getSearchResultFromCache(
+ searchString: CharSequence,
+ result: MutableList<SearchResult>
+ ) {
+ if (searchString.isNotEmpty()) {
+ searchResultCache.forEach {
+ val title = it.title
+ val summary = it.summary
+ if (title?.contains(
+ searchString,
+ ignoreCase = true
+ ) == true || summary?.contains(searchString, ignoreCase = true) == true
+ ) {
+ result.add(it)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/omnirom/control/search/SearchResult.kt b/app/src/main/java/org/omnirom/control/search/SearchResult.kt
new file mode 100644
index 0000000..88509ff
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/search/SearchResult.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The OmniROM Project
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.omnirom.control.search
+
+import androidx.annotation.DrawableRes
+
+data class SearchResult(
+ val title: String?,
+ val summary: String?,
+ @DrawableRes val iconResId: Int,
+ val className: String,
+ val key: String?
+)
diff --git a/app/src/main/java/org/omnirom/control/search/SearchResultAdapter.kt b/app/src/main/java/org/omnirom/control/search/SearchResultAdapter.kt
new file mode 100644
index 0000000..bc8fe92
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/search/SearchResultAdapter.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The OmniROM Project
+ *
+ * This program 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.
+ *
+ * This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.omnirom.control.search
+
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.RecyclerView
+import org.omnirom.control.R
+import org.omnirom.control.SettingsActivity
+
+class SearchResultAdapter(val result: List<SearchResult>, val activity: SettingsActivity) :
+ RecyclerView.Adapter<SearchResultAdapter.ViewHolder>() {
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val item: View = LayoutInflater.from(parent.context)
+ .inflate(R.layout.search_result_item, parent, false)
+ return ViewHolder(item)
+ }
+
+ override fun getItemCount(): Int {
+ return result.size
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val resultItem = result[position]
+ holder.imageView.setImageResource(resultItem.iconResId)
+ holder.titleText.text = resultItem.title ?: ""
+ if (resultItem.summary.isNullOrBlank()) {
+ holder.summaryText.visibility = View.GONE
+ } else {
+ holder.summaryText.visibility = View.VISIBLE
+ holder.summaryText.text = resultItem.summary
+ }
+
+ holder.itemView.setOnClickListener { v ->
+ val bundle = Bundle()
+ bundle.putString(PreferenceXmlParserUtils.METADATA_KEY, resultItem.key)
+ activity.supportFragmentManager
+ .beginTransaction()
+ .replace(
+ R.id.settings,
+ Class.forName(resultItem.className) as Class<out Fragment>, bundle
+ )
+ .addToBackStack(null)
+ .commit()
+
+ }
+ }
+
+ class ViewHolder(
+ view: View,
+ val imageView: ImageView = view.findViewById(R.id.search_result_icon),
+ val titleText: TextView = view.findViewById(R.id.search_result_title),
+ val summaryText: TextView = view.findViewById(R.id.search_result_summary)
+ ) : RecyclerView.ViewHolder(view)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/omnirom/control/widget/TintedDrawableSpan.java b/app/src/main/java/org/omnirom/control/widget/TintedDrawableSpan.java
new file mode 100644
index 0000000..1875938
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/widget/TintedDrawableSpan.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+package org.omnirom.control.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint.FontMetricsInt;
+import android.graphics.drawable.Drawable;
+import android.text.style.DynamicDrawableSpan;
+
+/**
+ * {@link DynamicDrawableSpan} which draws a drawable tinted with the current paint color.
+ */
+public class TintedDrawableSpan extends DynamicDrawableSpan {
+
+ private final Drawable mDrawable;
+ private int mOldTint;
+
+ public TintedDrawableSpan(Context context, int resourceId) {
+ super(ALIGN_BOTTOM);
+ mDrawable = context.getDrawable(resourceId).mutate();
+ mOldTint = 0;
+ mDrawable.setTint(0);
+ }
+
+ @Override
+ public int getSize(Paint paint, CharSequence text, int start, int end, FontMetricsInt fm) {
+ fm = fm == null ? paint.getFontMetricsInt() : fm;
+ int iconSize = fm.bottom - fm.top;
+ mDrawable.setBounds(0, 0, iconSize, iconSize);
+ return super.getSize(paint, text, start, end, fm);
+ }
+
+ @Override
+ public void draw(Canvas canvas, CharSequence text,
+ int start, int end, float x, int top, int y, int bottom, Paint paint) {
+ int color = paint.getColor();
+ if (mOldTint != color) {
+ mOldTint = color;
+ mDrawable.setTint(mOldTint);
+ }
+ super.draw(canvas, text, start, end, x, top, y, bottom, paint);
+ }
+
+ @Override
+ public Drawable getDrawable() {
+ return mDrawable;
+ }
+}
diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml
new file mode 100644
index 0000000..506c8bc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_search.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="?android:textColorPrimary"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z" />
+</vector>
diff --git a/app/src/main/res/drawable/search_hint_color.xml b/app/src/main/res/drawable/search_hint_color.xml
new file mode 100644
index 0000000..ba37c27
--- /dev/null
+++ b/app/src/main/res/drawable/search_hint_color.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@android:color/transparent" android:state_focused="true" />
+ <item android:color="?attr/colorOnSecondaryContainer"/>
+</selector>
\ No newline at end of file
diff --git a/app/src/main/res/drawable/text_field_corners.xml b/app/src/main/res/drawable/text_field_corners.xml
new file mode 100644
index 0000000..3034c1b
--- /dev/null
+++ b/app/src/main/res/drawable/text_field_corners.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?attr/colorSecondaryContainer" />
+ <corners android:radius="24dp" />
+ <stroke
+ android:width=".8dp"
+ android:color="?attr/colorOutline" />
+</shape>
\ No newline at end of file
diff --git a/app/src/main/res/layout/search_fragment.xml b/app/src/main/res/layout/search_fragment.xml
new file mode 100644
index 0000000..68b41c5
--- /dev/null
+++ b/app/src/main/res/layout/search_fragment.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/overlay_fragment_side_margin"
+ android:layout_marginEnd="@dimen/overlay_fragment_side_margin"
+ android:orientation="vertical">
+
+ <EditText
+ android:id="@+id/search_result_string"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"
+ android:layout_marginTop="15dp"
+ android:background="@drawable/text_field_corners"
+ android:ellipsize="start"
+ android:gravity="center_vertical"
+ android:hint="@string/search_result_hint"
+ android:paddingStart="16dp"
+ android:singleLine="true"
+ android:textColorHint="@drawable/search_hint_color" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/search_result_list"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginTop="@dimen/overlay_fragment_side_margin" />
+</LinearLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/search_result_item.xml b/app/src/main/res/layout/search_result_item.xml
new file mode 100644
index 0000000..7361142
--- /dev/null
+++ b/app/src/main/res/layout/search_result_item.xml
@@ -0,0 +1,45 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="8dp">
+
+ <ImageView
+ android:id="@+id/search_result_icon"
+ android:layout_width="@dimen/grid_item_icon_size"
+ android:layout_height="@dimen/grid_item_icon_size"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="@dimen/grid_icon_margin_start" />
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start|center_vertical"
+ android:layout_marginStart="@dimen/fragment_icon_margin_start"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/search_result_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:gravity="center_vertical"
+ android:maxLines="1"
+ android:textAppearance="@style/Theme.OmniControl.SearchResult.TitleTextStyle"
+ tools:text="aaaaa" />
+
+ <TextView
+ android:id="@+id/search_result_summary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:gravity="center_vertical"
+ android:maxLines="2"
+ android:textAppearance="@style/Theme.OmniControl.SearchResult.SummaryTextStyle"
+ android:textColor="?android:attr/textColorSecondary"
+ tools:text="bbbbb" />
+
+ </LinearLayout>
+</LinearLayout>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 903bb4f..4849bbd 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -30,4 +30,6 @@
<dimen name="toolbar_placeholder_height">105dp</dimen>
<dimen name="bottom_placeholder_height">60dp</dimen>
<dimen name="grid_item_margin_start">16dp</dimen>
+ <dimen name="search_result_title_text_size">16sp</dimen>
+ <dimen name="search_result_summary_text_size">12sp</dimen>
</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3917599..328c88c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -153,4 +153,8 @@
<string name="fingerprint_category_custom_icon">Custom icon</string>
<string name="custom_fp_icon_enable_title">Use custom icon</string>
<string name="custom_fp_icon_select_title">Select custom icon</string>
+
+ <string name="search_fragment_title">Search</string>
+ <string name="search_fragment_summary">Find settings by text</string>
+ <string name="search_result_hint">Search settings</string>
</resources>
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index c39d845..6d790d0 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -33,4 +33,12 @@
<style name="Theme.OmniControl.GridItem.SummaryTextStyle" parent="TextAppearance.Material3.BodyMedium">
<item name="android:textSize">@dimen/summary_text_size</item>
</style>
+
+ <style name="Theme.OmniControl.SearchResult.TitleTextStyle" parent="TextAppearance.Material3.BodyMedium">
+ <item name="android:textSize">@dimen/search_result_title_text_size</item>
+ </style>
+
+ <style name="Theme.OmniControl.SearchResult.SummaryTextStyle" parent="TextAppearance.Material3.BodyMedium">
+ <item name="android:textSize">@dimen/search_result_summary_text_size</item>
+ </style>
</resources>
\ No newline at end of file