Add dynamic Preferences indexing

- introduce a new private interface "Indexable".
- refactor Wallpaper and Wi-Fi settings to support this new
interface.
- only index saved/remembered Wi-Fi networks
- also add the capability to remove some data from the Index.

Fragments that want to publish some dynamic indexable data should
implement the "Indexable" interface and provide a static final field
named "INDEX_DATA_PROVIDER" with is the Indexable.IndexDataProvider
interface for providing the data for indexing.

Thru this interface the Index can ask what are the data chuncks to
index.

Change-Id: I31e7212c87b8218efe1a8f3028147cb19e119be6
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index 8582bc6..b3ea79a 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -95,7 +95,7 @@
 import com.android.settings.deviceinfo.UsbSettings;
 import com.android.settings.fuelgauge.PowerUsageSummary;
 import com.android.settings.indexer.Index;
-import com.android.settings.indexer.IndexableData;
+import com.android.settings.indexer.IndexableRef;
 import com.android.settings.inputmethod.InputMethodAndLanguageSettings;
 import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
 import com.android.settings.inputmethod.SpellCheckersSettings;
@@ -341,67 +341,72 @@
         }
     };
 
+    private static int NO_DATA_RES_ID = 0;
+
     /**
-     * Searchable data description.
+     * Indexable data description.
      *
      * Known restriction: we are only searching (for now) the first level of Settings.
      */
-    private static IndexableData[] INDEXABLE_DATA = new IndexableData[] {
-            new IndexableData(1, R.xml.wifi_settings,
+    private static IndexableRef[] INDEXABLE_REFS = new IndexableRef[] {
+            new IndexableRef(1, NO_DATA_RES_ID,
                     "com.android.settings.wifi.WifiSettings",
                     R.drawable.ic_settings_wireless),
-            new IndexableData(2, R.xml.bluetooth_settings,
+            new IndexableRef(2, R.xml.bluetooth_settings,
                     "com.android.settings.bluetooth.BluetoothSettings",
                     R.drawable.ic_settings_bluetooth2),
-            new IndexableData(3, R.xml.data_usage_metered_prefs,
+            new IndexableRef(3, R.xml.data_usage_metered_prefs,
                     "com.android.settings.net.DataUsageMeteredSettings",
                     R.drawable.ic_settings_data_usage),
-            new IndexableData(4, R.xml.wireless_settings,
+            new IndexableRef(4, R.xml.wireless_settings,
                     "com.android.settings.WirelessSettings",
                     R.drawable.empty_icon),
-            new IndexableData(5, R.xml.home_selection,
+            new IndexableRef(5, R.xml.home_selection,
                     "com.android.settings.HomeSettings",
                     R.drawable.ic_settings_home),
-            new IndexableData(6, R.xml.sound_settings,
+            new IndexableRef(6, R.xml.sound_settings,
                     "com.android.settings.SoundSettings",
                     R.drawable.ic_settings_sound),
-            new IndexableData(7, R.xml.display_settings,
+            new IndexableRef(7, R.xml.display_settings,
                     "com.android.settings.DisplaySettings",
                     R.drawable.ic_settings_display),
-            new IndexableData(8, R.xml.device_info_memory,
+            new IndexableRef(7, NO_DATA_RES_ID,
+                    "com.android.settings.WallpaperTypeSettings",
+                    R.drawable.ic_settings_display),
+            new IndexableRef(8, R.xml.device_info_memory,
                     "com.android.settings.deviceinfo.Memory",
                     R.drawable.ic_settings_storage),
-            new IndexableData(9, R.xml.power_usage_summary,
+            new IndexableRef(9, R.xml.power_usage_summary,
                     "com.android.settings.fuelgauge.PowerUsageSummary",
                     R.drawable.ic_settings_battery),
-            new IndexableData(10, R.xml.user_settings,
+            new IndexableRef(10, R.xml.user_settings,
                     "com.android.settings.users.UserSettings",
                     R.drawable.ic_settings_multiuser),
-            new IndexableData(11, R.xml.location_settings,
+            new IndexableRef(11, R.xml.location_settings,
                     "com.android.settings.location.LocationSettings",
                     R.drawable.ic_settings_location),
-            new IndexableData(12, R.xml.security_settings,
+            new IndexableRef(12, R.xml.security_settings,
                     "com.android.settings.SecuritySettings",
                     R.drawable.ic_settings_security),
-            new IndexableData(13, R.xml.language_settings,
+            new IndexableRef(13, R.xml.language_settings,
                     "com.android.settings.inputmethod.InputMethodAndLanguageSettings",
                     R.drawable.ic_settings_language),
-            new IndexableData(14, R.xml.privacy_settings,
+            new IndexableRef(14, R.xml.privacy_settings,
                     "com.android.settings.PrivacySettings",
                     R.drawable.ic_settings_backup),
-            new IndexableData(15, R.xml.date_time_prefs,
+            new IndexableRef(15, R.xml.date_time_prefs,
                     "com.android.settings.DateTimeSettings",
                     R.drawable.ic_settings_date_time),
-            new IndexableData(16, R.xml.accessibility_settings,
+            new IndexableRef(16, R.xml.accessibility_settings,
                     "com.android.settings.accessibility.AccessibilitySettings",
                     R.drawable.ic_settings_accessibility),
-            new IndexableData(17, R.xml.print_settings,
+            new IndexableRef(17, R.xml.print_settings,
                     "com.android.settings.print.PrintSettingsFragment",
                     com.android.internal.R.drawable.ic_print),
-            new IndexableData(18, R.xml.development_prefs,
+            new IndexableRef(18, R.xml.development_prefs,
                     "com.android.settings.DevelopmentSettings",
                     R.drawable.ic_settings_development),
-            new IndexableData(19, R.xml.device_info_settings,
+            new IndexableRef(19, R.xml.device_info_settings,
                     "com.android.settings.DeviceInfoSettings",
                     R.drawable.ic_settings_about),
     };
@@ -546,7 +551,7 @@
             getWindow().setUiOptions(getIntent().getIntExtra(EXTRA_UI_OPTIONS, 0));
         }
 
-        Index.getInstance(this).addIndexableData(INDEXABLE_DATA);
+        Index.getInstance(this).addIndexableData(INDEXABLE_REFS);
         Index.getInstance(this).update();
 
         mAuthenticatorHelper = new AuthenticatorHelper();
diff --git a/src/com/android/settings/WallpaperTypeSettings.java b/src/com/android/settings/WallpaperTypeSettings.java
index fa5f0ac..f46315a 100644
--- a/src/com/android/settings/WallpaperTypeSettings.java
+++ b/src/com/android/settings/WallpaperTypeSettings.java
@@ -16,18 +16,23 @@
 
 package com.android.settings;
 
-import android.app.Activity;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.preference.Preference;
 import android.preference.PreferenceScreen;
+import com.android.settings.indexer.Indexable;
+import com.android.settings.indexer.IndexableData;
+import com.android.settings.indexer.IndexableRef;
 
+import java.util.ArrayList;
 import java.util.List;
 
-public class WallpaperTypeSettings extends SettingsPreferenceFragment {
+public class WallpaperTypeSettings extends SettingsPreferenceFragment implements Indexable {
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -38,9 +43,9 @@
 
     private void populateWallpaperTypes() {
         // Search for activities that satisfy the ACTION_SET_WALLPAPER action
-        Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
+        final Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
         final PackageManager pm = getPackageManager();
-        List<ResolveInfo> rList = pm.queryIntentActivities(intent,
+        final List<ResolveInfo> rList = pm.queryIntentActivities(intent,
                 PackageManager.MATCH_DEFAULT_ONLY);
 
         final PreferenceScreen parent = getPreferenceScreen();
@@ -58,4 +63,42 @@
             parent.addPreference(pref);
         }
     }
+
+    public static final IndexDataProvider INDEX_DATA_PROVIDER =
+        new IndexDataProvider() {
+            @Override
+            public List<IndexableRef> getRefsToIndex(Context context) {
+                return null;
+            }
+
+            @Override
+            public List<IndexableData> getRawDataToIndex(Context context) {
+                final List<IndexableData> result = new ArrayList<IndexableData>();
+
+                final Intent intent = new Intent(Intent.ACTION_SET_WALLPAPER);
+                final PackageManager pm = context.getPackageManager();
+                final List<ResolveInfo> rList = pm.queryIntentActivities(intent,
+                        PackageManager.MATCH_DEFAULT_ONLY);
+
+                // Add indexable data for each of the matching activities
+                for (ResolveInfo info : rList) {
+                    Intent prefIntent = new Intent(intent);
+                    prefIntent.setComponent(new ComponentName(
+                            info.activityInfo.packageName, info.activityInfo.name));
+                    CharSequence label = info.loadLabel(pm);
+                    if (label == null) label = info.activityInfo.packageName;
+
+                    IndexableData data = new IndexableData();
+                    data.title = label.toString();
+                    data.fragmentTitle = context.getResources().getString(
+                            R.string.wallpaper_settings_fragment_title);
+                    data.intentAction = intent.getAction();
+                    data.intentTargetPackage = info.activityInfo.packageName;
+                    data.intentTargetClass = info.activityInfo.name;
+                    result.add(data);
+                }
+
+                return result;
+            }
+        };
 }
diff --git a/src/com/android/settings/indexer/Index.java b/src/com/android/settings/indexer/Index.java
index df1a58f..19b545f 100644
--- a/src/com/android/settings/indexer/Index.java
+++ b/src/com/android/settings/indexer/Index.java
@@ -34,6 +34,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
@@ -45,7 +46,7 @@
 
 public class Index {
 
-    private static final String LOG_TAG = "Indexer";
+    private static final String LOG_TAG = "Index";
 
     // Those indices should match the indices of SELECT_COLUMNS !
     public static final int COLUMN_INDEX_TITLE = 1;
@@ -81,7 +82,8 @@
     private static Index sInstance;
 
     private final AtomicBoolean mIsAvailable = new AtomicBoolean(false);
-    private final List<IndexableData> mDataToIndex = new ArrayList<IndexableData>();
+
+    private final UpdateData mUpdateData = new UpdateData();
 
     private final Context mContext;
 
@@ -103,6 +105,42 @@
         return mIsAvailable.get();
     }
 
+    public void addIndexableData(IndexableRef[] array) {
+        synchronized (mUpdateData) {
+            final int count = array.length;
+            for (int n = 0; n < count; n++) {
+                mUpdateData.dataToAdd.add(array[n]);
+            }
+        }
+    }
+
+    public void deleteIndexableData(String[] array) {
+        synchronized (mUpdateData) {
+            final int count = array.length;
+            for (int n = 0; n < count; n++) {
+                mUpdateData.dataToDelete.add(array[n]);
+            }
+        }
+    }
+
+    public boolean update() {
+        synchronized (mUpdateData) {
+            final UpdateIndexTask task = new UpdateIndexTask();
+            task.execute(mUpdateData);
+            try {
+                final boolean result = task.get();
+                mUpdateData.clear();
+                return result;
+            } catch (InterruptedException e) {
+                Log.e(LOG_TAG, "Cannot update index: " + e.getMessage());
+                return false;
+            } catch (ExecutionException e) {
+                Log.e(LOG_TAG, "Cannot update index: " + e.getMessage());
+                return false;
+            }
+        }
+    }
+
     public Cursor search(String query) {
         final String sql = buildSQL(query);
         Log.d(LOG_TAG, "Query: " + sql);
@@ -160,31 +198,6 @@
         return sb.toString();
     }
 
-    public void addIndexableData(IndexableData[] array) {
-        synchronized (mDataToIndex) {
-            final int count = array.length;
-            for (int n = 0; n < count; n++) {
-                mDataToIndex.add(array[n]);
-            }
-        }
-    }
-
-    public boolean update() {
-        synchronized (mDataToIndex) {
-            final IndexTask task = new IndexTask();
-            task.execute(mDataToIndex);
-            try {
-                return task.get();
-            } catch (InterruptedException e) {
-                Log.e(LOG_TAG, "Cannot update index: " + e.getMessage());
-                return false;
-            } catch (ExecutionException e) {
-                Log.e(LOG_TAG, "Cannot update index: " + e.getMessage());
-                return false;
-            }
-        }
-    }
-
     private SQLiteDatabase getReadableDatabase() {
         return IndexDatabaseHelper.getInstance(mContext).getReadableDatabase();
     }
@@ -194,9 +207,27 @@
     }
 
     /**
+     * A private class to describe the update data for the Index database
+     */
+    private class UpdateData {
+        public List<IndexableRef> dataToAdd;
+        public List<String> dataToDelete;
+
+        public UpdateData() {
+            dataToAdd = new ArrayList<IndexableRef>();
+            dataToDelete = new ArrayList<String>();
+        }
+
+        public void clear() {
+            dataToAdd.clear();
+            dataToDelete.clear();
+        }
+    }
+
+    /**
      * A private class for updating the Index database
      */
-    private class IndexTask extends AsyncTask<List<IndexableData>, Integer, Boolean> {
+    private class UpdateIndexTask extends AsyncTask<UpdateData, Integer, Boolean> {
 
         @Override
         protected void onPreExecute() {
@@ -211,45 +242,123 @@
         }
 
         @Override
-        protected Boolean doInBackground(List<IndexableData>... params) {
+        protected Boolean doInBackground(UpdateData... params) {
             boolean result = false;
-            final List<IndexableData> dataToIndex = params[0];
-            if (null == dataToIndex || dataToIndex.size() == 0) {
-                return result;
-            }
+
+            final List<IndexableRef> dataToAdd = params[0].dataToAdd;
+            final List<String> dataToDelete = params[0].dataToDelete;
             final SQLiteDatabase database = getWritableDatabase();
-            final Locale locale = Locale.getDefault();
-            final String localeStr = locale.toString();
-            if (isLocaleAlreadyIndexed(database, locale)) {
-                Log.d(LOG_TAG, "Locale '" + localeStr + "' is already indexed");
-                return true;
-            }
-            final long current = System.currentTimeMillis();
+            final String localeStr = Locale.getDefault().toString();
+
             try {
                 database.beginTransaction();
-                final int count = mDataToIndex.size();
-                for (int n = 0; n < count; n++) {
-                    final IndexableData data = mDataToIndex.get(n);
-                    indexFromResource(database, locale, data.xmlResId, data.fragmentName,
-                            data.iconResId, data.rank);
+                if (dataToAdd.size() > 0) {
+                    processDataToAdd(database, localeStr, dataToAdd);
+                }
+                if (dataToDelete.size() > 0) {
+                    processDataToDelete(database, localeStr, dataToDelete);
                 }
                 database.setTransactionSuccessful();
                 result = true;
             } finally {
                 database.endTransaction();
             }
+            return result;
+        }
+
+        private boolean processDataToDelete(SQLiteDatabase database, String localeStr,
+                                         List<String> dataToDelete) {
+
+            boolean result = false;
+            final long current = System.currentTimeMillis();
+
+            final int count = dataToDelete.size();
+            for (int n = 0; n < count; n++) {
+                final String data = dataToDelete.get(n);
+                delete(database, data);
+            }
+
+            final long now = System.currentTimeMillis();
+            Log.d(LOG_TAG, "Deleting data for locale '" + localeStr + "' took " +
+                    (now - current) + " millis");
+            return result;
+        }
+
+        private boolean processDataToAdd(SQLiteDatabase database, String localeStr,
+                                         List<IndexableRef> dataToAdd) {
+            if (isLocaleAlreadyIndexed(database, localeStr)) {
+                Log.d(LOG_TAG, "Locale '" + localeStr + "' is already indexed");
+                return true;
+            }
+
+            boolean result = false;
+            final long current = System.currentTimeMillis();
+
+            final int count = dataToAdd.size();
+            for (int n = 0; n < count; n++) {
+                final IndexableRef ref = dataToAdd.get(n);
+                indexOneRef(database, localeStr, ref);
+            }
+
             final long now = System.currentTimeMillis();
             Log.d(LOG_TAG, "Indexing locale '" + localeStr + "' took " +
                     (now - current) + " millis");
             return result;
         }
 
-        private boolean isLocaleAlreadyIndexed(SQLiteDatabase database, Locale locale) {
+        private void indexOneRef(SQLiteDatabase database, String localeStr, IndexableRef ref) {
+            if (ref.xmlResId > 0) {
+                indexFromResource(database, localeStr, ref.xmlResId, ref.fragmentName,
+                        ref.iconResId, ref.rank);
+            } else if (!TextUtils.isEmpty(ref.fragmentName)) {
+                indexRawData(database, localeStr, ref);
+            }
+        }
+
+        private void indexRawData(SQLiteDatabase database, String localeStr, IndexableRef ref) {
+            try {
+                final Class<?> clazz = Class.forName(ref.fragmentName);
+                if (Indexable.class.isAssignableFrom(clazz)) {
+                    final Field f = clazz.getField("INDEX_DATA_PROVIDER");
+                    final Indexable.IndexDataProvider provider =
+                            (Indexable.IndexDataProvider) f.get(null);
+
+                    final List<IndexableData> data = provider.getRawDataToIndex(mContext);
+
+                    final int size = data.size();
+                    for (int i = 0; i < size; i++) {
+                        IndexableData raw = data.get(i);
+
+                        // Should be the same locale as the one we are processing
+                        if (!raw.locale.toString().equalsIgnoreCase(localeStr)) {
+                            continue;
+                        }
+
+                        inserOneRowWithFilteredData(database, localeStr,
+                                raw.title,
+                                raw.summary,
+                                ref.fragmentName,
+                                raw.fragmentTitle,
+                                ref.iconResId,
+                                ref.rank,
+                                raw.keywords);
+                    }
+                }
+            } catch (ClassNotFoundException e) {
+                Log.e(LOG_TAG, "Cannot find class: " + ref.fragmentName, e);
+            } catch (NoSuchFieldException e) {
+                Log.e(LOG_TAG, "Cannot find field 'INDEX_DATA_PROVIDER'", e);
+            } catch (IllegalAccessException e) {
+                Log.e(LOG_TAG, "Illegal access to field 'INDEX_DATA_PROVIDER'", e);
+            }
+        }
+
+        private boolean isLocaleAlreadyIndexed(SQLiteDatabase database, String locale) {
             Cursor cursor = null;
             boolean result = false;
             final StringBuilder sb = new StringBuilder(IndexColumns.LOCALE);
             sb.append(" = ");
-            DatabaseUtils.appendEscapedSQLString(sb, locale.toString());
+            DatabaseUtils.appendEscapedSQLString(sb, locale);
             try {
                 // We care only for 1 row
                 cursor = database.query(Tables.TABLE_PREFS_INDEX, null,
@@ -264,10 +373,9 @@
             return result;
         }
 
-        private void indexFromResource(SQLiteDatabase database, Locale locale, int xmlResId,
+        private void indexFromResource(SQLiteDatabase database, String localeStr, int xmlResId,
                 String fragmentName, int iconResId, int rank) {
             XmlResourceParser parser = null;
-            final String localeStr = locale.toString();
             try {
                 parser = mContext.getResources().getXml(xmlResId);
 
@@ -373,6 +481,13 @@
             database.insertOrThrow(Tables.TABLE_PREFS_INDEX, null, values);
         }
 
+        private int delete(SQLiteDatabase database, String title) {
+            final String whereClause = IndexColumns.DATA_TITLE + "=?";
+            final String[] whereArgs = new String[] { title };
+
+            return database.delete(Tables.TABLE_PREFS_INDEX, whereClause, whereArgs);
+        }
+
         private String getDataTitle(AttributeSet attrs) {
             return getData(attrs,
                     com.android.internal.R.styleable.Preference,
diff --git a/src/com/android/settings/indexer/Indexable.java b/src/com/android/settings/indexer/Indexable.java
new file mode 100644
index 0000000..1b29b75
--- /dev/null
+++ b/src/com/android/settings/indexer/Indexable.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2014 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 com.android.settings.indexer;
+
+import android.content.Context;
+
+import java.util.List;
+
+/**
+ * Interface for classes whose instances can provide data for indexing.
+ *
+ * Classes implementing the Indexable interface must have a static field called
+ * <code>INDEX_DATA_PROVIDER</code>, which is an object implementing the
+ * {@link Indexable.IndexDataProvider Indexable.IndexDataProvider} interface.
+ *
+ * See {@link IndexableRef} and {@link IndexableData}.
+ *
+ */
+public interface Indexable {
+
+    public interface IndexDataProvider {
+        /**
+         * Return a list of references for indexing. See {@link IndexableRef}
+         *
+         * @param context the context
+         * @return a list of {@link IndexableRef} references. Can be null.
+         */
+        List<IndexableRef> getRefsToIndex(Context context);
+
+        /**
+         * Return a list of raw data for indexing. See {@link IndexableData}
+         *
+         * @param context the context
+         * @return a list of {@link IndexableData} references. Can be null.
+         */
+        List<IndexableData> getRawDataToIndex(Context context);
+    }
+}
diff --git a/src/com/android/settings/indexer/IndexableData.java b/src/com/android/settings/indexer/IndexableData.java
index 61714a2..ca388de 100644
--- a/src/com/android/settings/indexer/IndexableData.java
+++ b/src/com/android/settings/indexer/IndexableData.java
@@ -16,17 +16,30 @@
 
 package com.android.settings.indexer;
 
+import java.util.Locale;
+
+/**
+ * Indexable Data.
+ *
+ * This is the raw data used by the Indexer and should match its data model.
+ *
+ * See {@link Indexable} and {@link IndexableRef}.
+ */
 public class IndexableData {
 
-    public int rank;
-    public int xmlResId;
-    public String fragmentName;
-    public int iconResId;
+    public Locale locale;
 
-    public IndexableData(int rank, int dataResId, String name, int iconResId) {
-        this.rank = rank;
-        this.xmlResId = dataResId;
-        this.fragmentName = name;
-        this.iconResId = iconResId;
+    public String title;
+    public String summary;
+    public String keywords;
+
+    public String intentAction;
+    public String intentTargetPackage;
+    public String intentTargetClass;
+
+    public String fragmentTitle;
+
+    public IndexableData() {
+        locale = Locale.getDefault();
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/settings/indexer/IndexableRef.java b/src/com/android/settings/indexer/IndexableRef.java
new file mode 100644
index 0000000..c1ebcca
--- /dev/null
+++ b/src/com/android/settings/indexer/IndexableRef.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 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 com.android.settings.indexer;
+
+/**
+ * Indexable Reference.
+ *
+ * This class wraps a set of information representing data that can be indexed for a high
+ * level (see {@link android.preference.PreferenceScreen}).
+ *
+ * rank: is the rank of the data (basically its order in the list of Settings)
+ * xmlResId: is the resource Id of a PreferenceScreen xml file
+ * fragmentName: is the fragment class name associated with the data
+ * iconRedId: is the resource Id of an icon that represents the data
+ *
+ * See {@link Indexable} and {@link IndexableData}.
+ *
+ */
+public class IndexableRef {
+
+    public int rank;
+    public int xmlResId;
+    public String fragmentName;
+    public int iconResId;
+
+    public IndexableRef(int rank, int dataResId, String name, int iconResId) {
+        this.rank = rank;
+        this.xmlResId = dataResId;
+        this.fragmentName = name;
+        this.iconResId = iconResId;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java
index 41a4905..2ee73b5 100644
--- a/src/com/android/settings/wifi/WifiSettings.java
+++ b/src/com/android/settings/wifi/WifiSettings.java
@@ -23,6 +23,9 @@
 import com.android.settings.R;
 import com.android.settings.RestrictedSettingsFragment;
 import com.android.settings.SettingsActivity;
+import com.android.settings.indexer.Indexable;
+import com.android.settings.indexer.IndexableData;
+import com.android.settings.indexer.IndexableRef;
 import com.android.settings.wifi.p2p.WifiP2pSettings;
 
 import android.app.ActionBar;
@@ -90,7 +93,7 @@
  * and menus.
  */
 public class WifiSettings extends RestrictedSettingsFragment
-        implements DialogInterface.OnClickListener  {
+        implements DialogInterface.OnClickListener, Indexable  {
     private static final String TAG = "WifiSettings";
     private static final int MENU_ID_WPS_PBC = Menu.FIRST;
     private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
@@ -742,7 +745,8 @@
         switch (wifiState) {
             case WifiManager.WIFI_STATE_ENABLED:
                 // AccessPoints are automatically sorted with TreeSet.
-                final Collection<AccessPoint> accessPoints = constructAccessPoints();
+                final Collection<AccessPoint> accessPoints =
+                        constructAccessPoints(getActivity(), mWifiManager, mLastInfo, mLastState);
                 getPreferenceScreen().removeAll();
                 if(accessPoints.size() == 0) {
                     addMessagePreference(R.string.wifi_empty_list_wifi_on);
@@ -792,23 +796,26 @@
     }
 
     /** Returns sorted list of access points */
-    private List<AccessPoint> constructAccessPoints() {
+    private static List<AccessPoint> constructAccessPoints(Context context,
+            WifiManager wifiManager, WifiInfo lastInfo, DetailedState lastState) {
         ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
         /** Lookup table to more quickly update AccessPoints by only considering objects with the
          * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
         Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
 
-        final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
+        final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks();
         if (configs != null) {
             for (WifiConfiguration config : configs) {
-                AccessPoint accessPoint = new AccessPoint(getActivity(), config);
-                accessPoint.update(mLastInfo, mLastState);
+                AccessPoint accessPoint = new AccessPoint(context, config);
+                if (lastInfo != null && lastState != null) {
+                    accessPoint.update(lastInfo, lastState);
+                }
                 accessPoints.add(accessPoint);
                 apMap.put(accessPoint.ssid, accessPoint);
             }
         }
 
-        final List<ScanResult> results = mWifiManager.getScanResults();
+        final List<ScanResult> results = wifiManager.getScanResults();
         if (results != null) {
             for (ScanResult result : results) {
                 // Ignore hidden and ad-hoc networks.
@@ -823,7 +830,7 @@
                         found = true;
                 }
                 if (!found) {
-                    AccessPoint accessPoint = new AccessPoint(getActivity(), result);
+                    AccessPoint accessPoint = new AccessPoint(context, result);
                     accessPoints.add(accessPoint);
                     apMap.put(accessPoint.ssid, accessPoint);
                 }
@@ -836,7 +843,7 @@
     }
 
     /** A restricted multimap for use in constructAccessPoints */
-    private class Multimap<K,V> {
+    private static class Multimap<K,V> {
         private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
         /** retrieve a non-null list of values with key K */
         List<V> getAll(K key) {
@@ -1152,4 +1159,38 @@
         }
     }
 
+    public static final Indexable.IndexDataProvider INDEX_DATA_PROVIDER =
+        new Indexable.IndexDataProvider() {
+            @Override
+            public List<IndexableRef> getRefsToIndex(Context context) {
+                return null;
+            }
+
+            @Override
+            public List<IndexableData> getRawDataToIndex(Context context) {
+                final List<IndexableData> result = new ArrayList<IndexableData>();
+
+                // Add fragment title
+                IndexableData data = new IndexableData();
+                data.title = context.getResources().getString(R.string.wifi_settings);
+                data.fragmentTitle = context.getResources().getString(R.string.wifi_settings);
+                result.add(data);
+
+                // Add available Wi-Fi access points
+                WifiManager wifiManager =
+                        (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+                final Collection<AccessPoint> accessPoints =
+                        constructAccessPoints(context, wifiManager, null, null);
+                for (AccessPoint accessPoint : accessPoints) {
+                    // We are indexing only the saved Wi-Fi networks.
+                    if (accessPoint.getConfig() == null) continue;
+                    data = new IndexableData();
+                    data.title = accessPoint.getTitle().toString();
+                    data.fragmentTitle = context.getResources().getString(R.string.wifi_settings);
+                    result.add(data);
+                }
+
+                return result;
+            }
+        };
 }