cmparts: Make CMParts searchable
* Implement a SearchIndexablesProvider to allow the Settings app
to crawl our resources.
* Add all missing metadata where necessary so resources can be
indexed.
Change-Id: Ic8f304a7995b269f476eda6306d11b366621f4b0
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0edd219..b1360d0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -34,6 +34,7 @@
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
<uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+ <uses-permission android:name="android.permission.READ_SEARCH_INDEXABLES" />
<uses-permission android:name="cyanogenmod.permission.BIND_CORE_SERVICE" />
@@ -60,6 +61,17 @@
</intent-filter>
</receiver>
+ <provider android:name=".search.CMPartsSearchIndexablesProvider"
+ android:authorities="org.cyanogenmod.cmparts"
+ android:multiprocess="false"
+ android:grantUriPermissions="true"
+ android:permission="android.permission.READ_SEARCH_INDEXABLES"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" />
+ </intent-filter>
+ </provider>
+
<!-- Privacy settings (dashboard) -->
<activity-alias
android:name="PrivacySettings"
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 6f018b1..4737694 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -19,11 +19,6 @@
<attr name="icon" />
</declare-styleable>
- <!-- For Search -->
- <declare-styleable name="Preference">
- <attr name="keywords" format="string" />
- </declare-styleable>
-
<!-- For DotsPageIndicator -->
<declare-styleable name="DotsPageIndicator">
<attr name="dotDiameter" format="dimension" />
diff --git a/res/xml/anonymous_stats.xml b/res/xml/anonymous_stats.xml
index 30a65d5..b29885c 100644
--- a/res/xml/anonymous_stats.xml
+++ b/res/xml/anonymous_stats.xml
@@ -16,6 +16,7 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="cmstats"
android:title="@string/anonymous_statistics_title">
<cyanogenmod.preference.CMSecureSettingSwitchPreference
diff --git a/res/xml/appgroup_list.xml b/res/xml/appgroup_list.xml
index 848ce21..ee497a2 100644
--- a/res/xml/appgroup_list.xml
+++ b/res/xml/appgroup_list.xml
@@ -17,6 +17,6 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="profile_appgroups_list"
- xmlns:settings="http://schemas.android.com/apk/res/com.android.settings">
+ android:title="@string/profile_appgroups_title">
-</PreferenceScreen>
\ No newline at end of file
+</PreferenceScreen>
diff --git a/res/xml/application_list.xml b/res/xml/application_list.xml
index 43c506f..6df1234 100644
--- a/res/xml/application_list.xml
+++ b/res/xml/application_list.xml
@@ -14,8 +14,7 @@
limitations under the License.
-->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res/com.android.settings">
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="general_section"
@@ -27,4 +26,4 @@
android:title="@string/profile_applist_title" >
</PreferenceCategory>
-</PreferenceScreen>
\ No newline at end of file
+</PreferenceScreen>
diff --git a/res/xml/battery_light_settings.xml b/res/xml/battery_light_settings.xml
index 34b74d0..454ab1a 100644
--- a/res/xml/battery_light_settings.xml
+++ b/res/xml/battery_light_settings.xml
@@ -15,8 +15,7 @@
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
- android:key="battery_light"
+ android:key="battery_lights"
android:title="@string/battery_light_title">
<PreferenceCategory
diff --git a/res/xml/button_settings.xml b/res/xml/button_settings.xml
index c58650e..de9c062 100644
--- a/res/xml/button_settings.xml
+++ b/res/xml/button_settings.xml
@@ -14,7 +14,9 @@
limitations under the License.
-->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="button_settings"
+ android:title="@string/button_pref_title">
<SwitchPreference
android:key="disable_nav_keys"
diff --git a/res/xml/display_rotation.xml b/res/xml/display_rotation.xml
index b36df56..1ce81e8 100644
--- a/res/xml/display_rotation.xml
+++ b/res/xml/display_rotation.xml
@@ -15,6 +15,7 @@
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="rotation"
android:title="@string/display_rotation_title">
<SwitchPreference
diff --git a/res/xml/livedisplay.xml b/res/xml/livedisplay.xml
index ef1d9bd..443e17a 100644
--- a/res/xml/livedisplay.xml
+++ b/res/xml/livedisplay.xml
@@ -14,7 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="livedisplay"
+ android:title="@*cyanogenmod.platform:string/live_display_title">
<PreferenceCategory
android:key="live_display_options"
diff --git a/res/xml/notification_light_settings.xml b/res/xml/notification_light_settings.xml
index 7035490..b9e0ce1 100644
--- a/res/xml/notification_light_settings.xml
+++ b/res/xml/notification_light_settings.xml
@@ -15,7 +15,7 @@
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
- android:key="notification_light"
+ android:key="notification_lights"
android:title="@string/notification_light_title">
<PreferenceCategory
diff --git a/res/xml/parts_catalog.xml b/res/xml/parts_catalog.xml
index 94d6a7a..8fec12f 100644
--- a/res/xml/parts_catalog.xml
+++ b/res/xml/parts_catalog.xml
@@ -15,15 +15,28 @@
limitations under the License.
-->
-<parts-catalog xmlns:android="http://schemas.android.com/apk/res/android">
+<!--
+ The parts catalog is used to locate items (usually a PreferenceScreen) inside
+ of CMParts. This can be used by CMPartsPreference to create a simple, two-line
+ entry point from Settings or another application. All entries should specify
+ a fragment, which is a SettingsPreferenceFragment subclass inside CMParts.
+
+ Metadata for the search index provider should be provided for all parts. This
+ can be supplied an XML resource in the "cm:xmlRes" attribute or by implementing
+ the Searchable interface.
+-->
+<parts-catalog xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:cm="http://schemas.android.com/apk/res/cyanogenmod.platform">
<part android:key="battery_lights"
android:title="@string/battery_light_title"
- android:fragment="org.cyanogenmod.cmparts.notificationlight.BatteryLightSettings" />
+ android:fragment="org.cyanogenmod.cmparts.notificationlight.BatteryLightSettings"
+ cm:xmlRes="@xml/battery_light_settings" />
<part android:key="button_settings"
android:title="@string/button_pref_title"
- android:fragment="org.cyanogenmod.cmparts.input.ButtonSettings" />
+ android:fragment="org.cyanogenmod.cmparts.input.ButtonSettings"
+ cm:xmlRes="@xml/button_settings" />
<part android:key="contributors"
android:title="@string/contributors_cloud_fragment_title"
@@ -32,26 +45,47 @@
<part android:key="livedisplay"
android:title="@*cyanogenmod.platform:string/live_display_title"
android:summary="@string/live_display_summary"
- android:fragment="org.cyanogenmod.cmparts.livedisplay.LiveDisplay" />
+ android:fragment="org.cyanogenmod.cmparts.livedisplay.LiveDisplay"
+ cm:xmlRes="@xml/livedisplay" />
<part android:key="notification_lights"
android:title="@string/notification_light_title"
- android:fragment="org.cyanogenmod.cmparts.notificationlight.NotificationLightSettings" />
+ android:fragment="org.cyanogenmod.cmparts.notificationlight.NotificationLightSettings"
+ cm:xmlRes="@xml/notification_light_settings" />
<part android:key="privacy_settings"
android:title="@string/privacy_settings_title"
- android:fragment="org.cyanogenmod.cmparts.PrivacySettings" />
+ android:fragment="org.cyanogenmod.cmparts.PrivacySettings"
+ cm:xmlRes="@xml/privacy_settings" />
<part android:key="profiles_settings"
android:title="@string/profiles_settings_title"
- android:fragment="org.cyanogenmod.cmparts.profiles.ProfilesSettings" />
+ android:fragment="org.cyanogenmod.cmparts.profiles.ProfilesSettings"
+ cm:xmlRes="@xml/profiles_settings" />
<part android:key="rotation"
android:title="@string/display_rotation_title"
- android:fragment="org.cyanogenmod.cmparts.hardware.DisplayRotation" />
+ android:fragment="org.cyanogenmod.cmparts.hardware.DisplayRotation"
+ cm:xmlRes="@xml/display_rotation" />
<part android:key="status_bar_settings"
android:title="@string/status_bar_title"
- android:fragment="org.cyanogenmod.cmparts.statusbar.StatusBarSettings" />
+ android:fragment="org.cyanogenmod.cmparts.statusbar.StatusBarSettings"
+ cm:xmlRes="@xml/status_bar_settings" />
+
+ <part android:key="cmstats"
+ android:title="@string/anonymous_statistics_title"
+ android:fragment="org.cyanogenmod.cmparts.cmstats.AnonymousStats"
+ cm:xmlRes="@xml/anonymous_stats" />
+
+ <part android:key="power_menu"
+ android:title="@string/power_menu_title"
+ android:fragment="org.cyanogenmod.cmparts.input.PowerMenuActions"
+ cm:xmlRes="@xml/power_menu_settings" />
+
+ <part android:key="privacy_guard_manager"
+ android:title="@*cyanogenmod.platform:string/privacy_guard_manager_title"
+ android:fragment="org.cyanogenmod.cmparts.privacyguard.PrivacyGuardManager"
+ cm:xmlRes="@xml/privacy_guard_prefs" />
</parts-catalog>
diff --git a/res/xml/power_menu_settings.xml b/res/xml/power_menu_settings.xml
index 36e1d65..5843c61 100644
--- a/res/xml/power_menu_settings.xml
+++ b/res/xml/power_menu_settings.xml
@@ -16,6 +16,7 @@
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="power_menu"
android:title="@string/power_menu_title">
<CheckBoxPreference
diff --git a/res/xml/privacy_guard_prefs.xml b/res/xml/privacy_guard_prefs.xml
index f77cdfc..ad91bef 100644
--- a/res/xml/privacy_guard_prefs.xml
+++ b/res/xml/privacy_guard_prefs.xml
@@ -15,7 +15,9 @@
-->
<PreferenceScreen
- xmlns:android="http://schemas.android.com/apk/res/android">
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="privacy_guard_manager"
+ android:title="@*cyanogenmod.platform:string/privacy_guard_manager_title">
<SwitchPreference
android:key="privacy_guard_default"
diff --git a/res/xml/profiles_settings.xml b/res/xml/profiles_settings.xml
index d3c477c..94c6deb 100644
--- a/res/xml/profiles_settings.xml
+++ b/res/xml/profiles_settings.xml
@@ -16,5 +16,5 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:settings="http://schemas.android.com/apk/res/com.android.settings"
- android:key="profiles_list" />
+ android:key="profiles_settings"
+ android:title="@string/profiles_settings_title" />
diff --git a/res/xml/status_bar_settings.xml b/res/xml/status_bar_settings.xml
index 6569250..13f302b 100644
--- a/res/xml/status_bar_settings.xml
+++ b/res/xml/status_bar_settings.xml
@@ -16,6 +16,7 @@
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="status_bar_settings"
android:title="@string/status_bar_title">
<PreferenceScreen
diff --git a/src/org/cyanogenmod/cmparts/contributors/ContributorsCloudFragment.java b/src/org/cyanogenmod/cmparts/contributors/ContributorsCloudFragment.java
index 21864ba..c2cd9ec 100644
--- a/src/org/cyanogenmod/cmparts/contributors/ContributorsCloudFragment.java
+++ b/src/org/cyanogenmod/cmparts/contributors/ContributorsCloudFragment.java
@@ -40,6 +40,7 @@
import android.text.Html;
import android.text.TextUtils;
import android.text.format.DateFormat;
+import android.util.ArraySet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -50,14 +51,16 @@
import android.view.WindowManager;
import android.view.animation.LinearInterpolator;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.TextView;
-import android.widget.AdapterView.OnItemClickListener;
import org.cyanogenmod.cmparts.R;
+import org.cyanogenmod.cmparts.search.BaseSearchIndexProvider;
+import org.cyanogenmod.cmparts.search.Searchable;
import java.io.File;
import java.io.FileOutputStream;
@@ -68,9 +71,10 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
public class ContributorsCloudFragment extends Fragment implements SearchView.OnQueryTextListener,
- SearchView.OnCloseListener, MenuItem.OnActionExpandListener {
+ SearchView.OnCloseListener, MenuItem.OnActionExpandListener, Searchable {
private static final String TAG = "ContributorsCloud";
@@ -767,4 +771,41 @@
}
}
}
+
+ public static final Searchable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+ new BaseSearchIndexProvider() {
+
+ @Override
+ public Set<String> getSearchKeywords(Context context) {
+
+ // Index the top 100 contributors, for fun :)
+ File dbPath = context.getDatabasePath(DB_NAME);
+ SQLiteDatabase db = null;
+ try {
+ db = SQLiteDatabase.openDatabase(dbPath.getAbsolutePath(),
+ null, SQLiteDatabase.OPEN_READONLY);
+ if (db == null) {
+ Log.e(TAG, "Cannot open cloud database: " + DB_NAME + ". db == null");
+ return null;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage(), e);
+ if (db != null && db.isOpen()) {
+ db.close();
+ }
+ return null;
+ }
+
+ Set<String> result = new ArraySet<>();
+ Cursor c = db.rawQuery(
+ "select username from metadata order by commits desc limit 100;", null);
+ while (c.moveToNext()) {
+ result.add(c.getString(0));
+ }
+ c.close();
+ db.close();
+
+ return result;
+ }
+ };
}
diff --git a/src/org/cyanogenmod/cmparts/livedisplay/LiveDisplay.java b/src/org/cyanogenmod/cmparts/livedisplay/LiveDisplay.java
index 5858e59..4b3fec5 100644
--- a/src/org/cyanogenmod/cmparts/livedisplay/LiveDisplay.java
+++ b/src/org/cyanogenmod/cmparts/livedisplay/LiveDisplay.java
@@ -23,20 +23,21 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.UserHandle;
+import android.support.v14.preference.SwitchPreference;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceCategory;
-import android.support.v14.preference.SwitchPreference;
-import android.provider.SearchIndexableResource;
+import android.util.ArraySet;
import android.util.Log;
import com.android.internal.util.ArrayUtils;
import org.cyanogenmod.cmparts.R;
import org.cyanogenmod.cmparts.SettingsPreferenceFragment;
+import org.cyanogenmod.cmparts.search.BaseSearchIndexProvider;
+import org.cyanogenmod.cmparts.search.Searchable;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Set;
import cyanogenmod.hardware.CMHardwareManager;
import cyanogenmod.hardware.DisplayMode;
@@ -52,7 +53,7 @@
import static cyanogenmod.hardware.LiveDisplayManager.MODE_OFF;
import static cyanogenmod.hardware.LiveDisplayManager.MODE_OUTDOOR;
-public class LiveDisplay extends SettingsPreferenceFragment implements
+public class LiveDisplay extends SettingsPreferenceFragment implements Searchable,
Preference.OnPreferenceChangeListener {
private static final String TAG = "LiveDisplay";
@@ -72,6 +73,12 @@
private static final String KEY_LIVE_DISPLAY_COLOR_PROFILE = "live_display_color_profile";
+ private static final String COLOR_PROFILE_TITLE =
+ KEY_LIVE_DISPLAY_COLOR_PROFILE + "_%s_title";
+
+ private static final String COLOR_PROFILE_SUMMARY =
+ KEY_LIVE_DISPLAY_COLOR_PROFILE + "_%s_summary";
+
private final Handler mHandler = new Handler();
private final SettingsObserver mObserver = new SettingsObserver();
@@ -214,8 +221,8 @@
mObserver.register(false);
}
- private String getStringForResourceName(String resourceName, String defaultValue) {
- Resources res = getResources();
+ private static String getStringForResourceName(Resources res,
+ String resourceName, String defaultValue) {
int resId = res.getIdentifier(resourceName, "string", "org.cyanogenmod.cmparts");
if (resId <= 0) {
Log.e(TAG, "No resource found for " + resourceName);
@@ -225,6 +232,18 @@
}
}
+ private static String getLocalizedProfileName(Resources res, String profileName) {
+ String name = profileName.toLowerCase().replace(" ", "_");
+ String nameRes = String.format(COLOR_PROFILE_TITLE, name);
+ return getStringForResourceName(res, nameRes, profileName);
+ }
+
+ private static String getLocalizedProfileSummary(Resources res, String profileName) {
+ String name = profileName.toLowerCase().replace(" ", "_");
+ String summaryRes = String.format(COLOR_PROFILE_SUMMARY, name);
+ return getStringForResourceName(res, summaryRes, null);
+ }
+
private boolean updateDisplayModes() {
final DisplayMode[] modes = mHardware.getDisplayModes();
if (modes == null || modes.length == 0) {
@@ -239,13 +258,10 @@
mColorProfileSummaries = new String[modes.length];
for (int i = 0; i < modes.length; i++) {
values[i] = String.valueOf(modes[i].id);
- String name = modes[i].name.toLowerCase().replace(" ", "_");
- String nameRes = String.format("live_display_color_profile_%s_title", name);
- entries[i] = getStringForResourceName(nameRes, modes[i].name);
+ entries[i] = getLocalizedProfileName(getResources(), modes[i].name);
// Populate summary
- String summaryRes = String.format("live_display_color_profile_%s_summary", name);
- String summary = getStringForResourceName(summaryRes, null);
+ String summary = getLocalizedProfileSummary(getResources(), modes[i].name);
if (summary != null) {
summary = String.format("%s - %s", entries[i], summary);
}
@@ -360,38 +376,22 @@
@Override
public void onChange(boolean selfChange, Uri uri) {
- super.onChange(selfChange, uri);
+ super.onChange(selfChange, uri);
updateModeSummary();
updateTemperatureSummary();
}
}
- /*
- * Disabled until search query is implemented
- *
- public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
+
+ public static final Searchable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
- public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
- boolean enabled) {
- ArrayList<SearchIndexableResource> result =
- new ArrayList<SearchIndexableResource>();
-
- SearchIndexableResource sir = new SearchIndexableResource(context);
- sir.xmlResId = R.xml.livedisplay;
- result.add(sir);
-
- return result;
- }
-
- @Override
- public List<String> getNonIndexableKeys(Context context) {
- final CMHardwareManager hardware = CMHardwareManager.getInstance(context);
+ public Set<String> getNonIndexableKeys(Context context) {
final LiveDisplayConfig config = LiveDisplayManager.getInstance(context).getConfig();
+ final Set<String> result = new ArraySet<String>();
- ArrayList<String> result = new ArrayList<String>();
- if (!hardware.isSupported(FEATURE_DISPLAY_MODES)) {
+ if (!config.hasFeature(FEATURE_DISPLAY_MODES)) {
result.add(KEY_LIVE_DISPLAY_COLOR_PROFILE);
}
if (!config.hasFeature(MODE_OUTDOOR)) {
@@ -411,6 +411,22 @@
}
return result;
}
+
+ @Override
+ public Set<String> getSearchKeywords(Context context) {
+ final LiveDisplayConfig config = LiveDisplayManager.getInstance(context).getConfig();
+ final Set<String> result = new ArraySet<>();
+
+ // Add keywords for supported color profiles
+ if (config.hasFeature(FEATURE_DISPLAY_MODES)) {
+ DisplayMode[] modes = CMHardwareManager.getInstance(context).getDisplayModes();
+ if (modes != null && modes.length > 0) {
+ for (DisplayMode mode : modes) {
+ result.add(getLocalizedProfileName(context.getResources(), mode.name));
+ }
+ }
+ }
+ return result;
+ }
};
- */
}
diff --git a/src/org/cyanogenmod/cmparts/search/BaseSearchIndexProvider.java b/src/org/cyanogenmod/cmparts/search/BaseSearchIndexProvider.java
new file mode 100644
index 0000000..5791c4c
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/search/BaseSearchIndexProvider.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod 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.cyanogenmod.cmparts.search;
+
+import android.content.Context;
+
+import java.util.Set;
+
+/**
+ * Convenience class which can be used to return additional search metadata without
+ * having to implement all methods.
+ */
+public class BaseSearchIndexProvider implements Searchable.SearchIndexProvider {
+
+ @Override
+ public Set<String> getSearchKeywords(Context context) {
+ return null;
+ }
+
+ @Override
+ public Set<String> getNonIndexableKeys(Context context) {
+ return null;
+ }
+}
diff --git a/src/org/cyanogenmod/cmparts/search/CMPartsSearchIndexablesProvider.java b/src/org/cyanogenmod/cmparts/search/CMPartsSearchIndexablesProvider.java
new file mode 100644
index 0000000..5e586b7
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/search/CMPartsSearchIndexablesProvider.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod 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.cyanogenmod.cmparts.search;
+
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.provider.SearchIndexablesProvider;
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+
+import org.cyanogenmod.cmparts.PartsActivity;
+import org.cyanogenmod.cmparts.search.Searchable.SearchIndexProvider;
+import org.cyanogenmod.internal.cmparts.PartInfo;
+import org.cyanogenmod.platform.internal.R;
+
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.Set;
+
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ICON_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_ACTION;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_CLASS;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEY;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_KEYWORDS;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_RANK;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_ON;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_TITLE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_USER_ID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_CLASS_NAME;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_ICON_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_ACTION;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RESID;
+import static android.provider.SearchIndexablesContract.INDEXABLES_RAW_COLUMNS;
+import static android.provider.SearchIndexablesContract.INDEXABLES_XML_RES_COLUMNS;
+import static android.provider.SearchIndexablesContract.NON_INDEXABLES_KEYS_COLUMNS;
+import static org.cyanogenmod.internal.cmparts.PartsList.CMPARTS_ACTIVITY;
+import static org.cyanogenmod.internal.cmparts.PartsList.CMPARTS_PACKAGE;
+import static org.cyanogenmod.internal.cmparts.PartsList.getPartInfo;
+import static org.cyanogenmod.internal.cmparts.PartsList.getPartsList;
+
+/**
+ * Provides search metadata to the Settings app
+ */
+public class CMPartsSearchIndexablesProvider extends SearchIndexablesProvider {
+
+ private static final String TAG = CMPartsSearchIndexablesProvider.class.getSimpleName();
+
+ private static final String FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER =
+ "SEARCH_INDEX_DATA_PROVIDER";
+
+ @Override
+ public Cursor queryXmlResources(String[] strings) {
+ MatrixCursor cursor = new MatrixCursor(INDEXABLES_XML_RES_COLUMNS);
+ final Set<String> keys = getPartsList(getContext());
+
+ // return all of the xml resources listed in the resource: attribute
+ // from parts_catalog.xml for indexing
+ for (String key : keys) {
+ PartInfo i = getPartInfo(getContext(), key);
+ if (i == null || i.getXmlRes() <= 0) {
+ continue;
+ }
+
+ Object[] ref = new Object[7];
+ ref[COLUMN_INDEX_XML_RES_RANK] = 2;
+ ref[COLUMN_INDEX_XML_RES_RESID] = i.getXmlRes();
+ ref[COLUMN_INDEX_XML_RES_CLASS_NAME] = null;
+ ref[COLUMN_INDEX_XML_RES_ICON_RESID] = R.drawable.ic_launcher_cyanogenmod;
+ ref[COLUMN_INDEX_XML_RES_INTENT_ACTION] = i.getAction();
+ ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE] = CMPARTS_ACTIVITY.getPackageName();
+ ref[COLUMN_INDEX_XML_RES_INTENT_TARGET_CLASS] = CMPARTS_ACTIVITY.getClassName();
+ cursor.addRow(ref);
+ }
+ return cursor;
+
+ }
+
+ @Override
+ public Cursor queryRawData(String[] strings) {
+ MatrixCursor cursor = new MatrixCursor(INDEXABLES_RAW_COLUMNS);
+ final Set<String> keys = getPartsList(getContext());
+
+ // we also submit keywords and metadata for all top-level items
+ // which don't have an associated XML resource
+ for (String key : keys) {
+ PartInfo i = getPartInfo(getContext(), key);
+ if (i == null) {
+ continue;
+ }
+
+ // look for custom keywords
+ SearchIndexProvider sip = getSearchIndexProvider(i.getFragmentClass());
+ if (sip == null) {
+ continue;
+ }
+
+ // don't create a duplicate entry if no custom keywords are provided
+ // and a resource was already indexed
+ Set<String> keywordList = sip.getSearchKeywords(getContext());
+ if ((keywordList == null || keywordList.size() == 0) && i.getXmlRes() > 0) {
+ continue;
+ }
+
+ String keywords = null;
+ if (keywordList != null && keywordList.size() > 0) {
+ keywords = TextUtils.join(" ", keywordList);
+ }
+
+ Object[] ref = new Object[14];
+ ref[COLUMN_INDEX_RAW_RANK] = 2;
+ ref[COLUMN_INDEX_RAW_TITLE] = i.getTitle();
+ ref[COLUMN_INDEX_RAW_SUMMARY_ON] = i.getSummary();
+ ref[COLUMN_INDEX_RAW_KEYWORDS] = keywords;
+ ref[COLUMN_INDEX_RAW_ICON_RESID] = i.getIconRes() > 0 ? i.getIconRes() :
+ R.drawable.ic_launcher_cyanogenmod;
+ ref[COLUMN_INDEX_RAW_INTENT_ACTION] = i.getAction();
+ ref[COLUMN_INDEX_RAW_INTENT_TARGET_PACKAGE] = CMPARTS_ACTIVITY.getPackageName();
+ ref[COLUMN_INDEX_RAW_INTENT_TARGET_CLASS] = CMPARTS_ACTIVITY.getClassName();
+ ref[COLUMN_INDEX_RAW_KEY] = i.getName();
+ ref[COLUMN_INDEX_RAW_USER_ID] = -1;
+ cursor.addRow(ref);
+ }
+ return cursor;
+ }
+
+ @Override
+ public Cursor queryNonIndexableKeys(String[] strings) {
+ MatrixCursor cursor = new MatrixCursor(NON_INDEXABLES_KEYS_COLUMNS);
+
+ final Set<String> keys = getPartsList(getContext());
+ final Set<String> nonIndexables = new ArraySet<>();
+
+ for (String key : keys) {
+ PartInfo i = getPartInfo(getContext(), key);
+ if (i == null) {
+ continue;
+ }
+
+ // look for non-indexable keys
+ SearchIndexProvider sip = getSearchIndexProvider(i.getFragmentClass());
+ if (sip == null) {
+ continue;
+ }
+
+ Set<String> nik = sip.getNonIndexableKeys(getContext());
+ if (nik == null) {
+ continue;
+ }
+
+ nonIndexables.addAll(nik);
+ }
+
+ for (String nik : nonIndexables) {
+ Object[] ref = new Object[1];
+ ref[COLUMN_INDEX_NON_INDEXABLE_KEYS_KEY_VALUE] = nik;
+ cursor.addRow(ref);
+ }
+ return cursor;
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ private SearchIndexProvider getSearchIndexProvider(final String className) {
+
+ final Class<?> clazz;
+ try {
+ clazz = Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ Log.d(TAG, "Cannot find class: " + className);
+ return null;
+ }
+
+ if (clazz == null || !Searchable.class.isAssignableFrom(clazz)) {
+ return null;
+ }
+
+ try {
+ final Field f = clazz.getField(FIELD_NAME_SEARCH_INDEX_DATA_PROVIDER);
+ return (SearchIndexProvider) f.get(null);
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage(), e);
+ }
+ return null;
+ }
+}
diff --git a/src/org/cyanogenmod/cmparts/search/Searchable.java b/src/org/cyanogenmod/cmparts/search/Searchable.java
new file mode 100644
index 0000000..9e69855
--- /dev/null
+++ b/src/org/cyanogenmod/cmparts/search/Searchable.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The CyanogenMod 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.cyanogenmod.cmparts.search;
+
+import android.content.Context;
+
+import java.util.Set;
+
+/**
+ * This interface should be implemented by classes which want to provide additional
+ * dynamic metadata to the indexer. Since our entrypoints are standardized around
+ * the parts catalog, there is no need to enumerate XML resources here. Keywords
+ * and non-indexable keys may be supplied by a class.
+ *
+ * If a class wants to use this functionality, it should contain a static field
+ * named SEARCH_INDEX_DATA_PROVIDER which contains an instance of SearchIndexProvider.
+ * This is similar to the mechanism used by the Settings app.
+ */
+public interface Searchable {
+
+ public interface SearchIndexProvider {
+
+ public Set<String> getSearchKeywords(Context context);
+
+ public Set<String> getNonIndexableKeys(Context context);
+ }
+}