Fork Search code to independantly develop and test search.

This is the start of the new search in Settings. It is a nearly complete
replacement of the old search code in a more modular and flexible
architecture. It is expanding the datasources that it queries, including
the same Settings database, which will now include more first party apps
and be extended to support inline results where the user can change
settings directly from the search view. Search will also fan out to
query new sources (local or remote), and is built in a way
such that adding additional sources is roughly the same amount of work
had they been added in the initial writing of this code.

Query interpretation will now be source-dependant, allowing for future
upgrades to fuzzy search where it is applicable.

Change-Id: Ib0bac1fe92bf8a662d33abf9a99bb6ee2090ec8f
Fixes: 32115225, 32378927
Test: make RunSettingsRoboTests
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index f67f73f..1831755 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -138,6 +138,8 @@
 import com.android.settings.qstile.DevelopmentTiles;
 import com.android.settings.search.DynamicIndexableContentMonitor;
 import com.android.settings.search.Index;
+import com.android.settings.search2.SearchFeatureProvider;
+import com.android.settings.search2.SearchFragment;
 import com.android.settings.sim.SimSettings;
 import com.android.settings.system.SystemDashboardFragment;
 import com.android.settings.tts.TextToSpeechSettings;
@@ -479,6 +481,8 @@
     private SearchResultsSummary mSearchResultsFragment;
     private String mSearchQuery;
 
+    private SearchFeatureProvider mSearchFeatureProvider;
+
     // Categories
     private ArrayList<DashboardCategory> mCategories = new ArrayList<DashboardCategory>();
 
@@ -528,9 +532,14 @@
         }
 
         MenuInflater inflater = getMenuInflater();
+        if (mSearchFeatureProvider.isEnabled()) {
+            mSearchFeatureProvider.setUpSearchMenu(menu, this);
+            return true;
+        }
         inflater.inflate(R.menu.options_menu, menu);
 
-        // Cache the search query (can be overriden by the OnQueryTextListener)
+
+        // Cache the search query (can be overridden by the OnQueryTextListener)
         final String query = mSearchQuery;
 
         mSearchMenuItem = menu.findItem(R.id.search);
@@ -553,7 +562,6 @@
             mSearchMenuItem.expandActionView();
         }
         mSearchView.setQuery(query, true /* submit */);
-
         return true;
     }
 
@@ -596,8 +604,12 @@
     protected void onCreate(Bundle savedState) {
         super.onCreate(savedState);
         long startTime = System.currentTimeMillis();
-        mDashboardFeatureProvider =
-                FeatureFactory.getFactory(this).getDashboardFeatureProvider(this);
+
+        final FeatureFactory factory = FeatureFactory.getFactory(this);
+
+        mDashboardFeatureProvider = factory.getDashboardFeatureProvider(this);
+        mSearchFeatureProvider = factory.getSearchFeatureProvider(this);
+
         // Should happen before any call to getIntent()
         getMetaData();
 
@@ -1274,19 +1286,24 @@
         return super.shouldUpRecreateTask(new Intent(this, SettingsActivity.class));
     }
 
+    @Deprecated
     @Override
     public boolean onQueryTextSubmit(String query) {
-        switchToSearchResultsFragmentIfNeeded();
+        if (mSearchFeatureProvider.isEnabled()) {
+            return false;
+        }
         mSearchQuery = query;
+        switchToSearchResultsFragmentIfNeeded();
         return mSearchResultsFragment.onQueryTextSubmit(query);
     }
 
+    @Deprecated
     @Override
     public boolean onQueryTextChange(String newText) {
-        mSearchQuery = newText;
-        if (mSearchResultsFragment == null) {
+        if (mSearchFeatureProvider.isEnabled() || mSearchResultsFragment == null) {
             return false;
         }
+        mSearchQuery = newText;
         return mSearchResultsFragment.onQueryTextChange(newText);
     }
 
@@ -1330,6 +1347,7 @@
         }
     }
 
+    @Deprecated
     private void switchToSearchResultsFragmentIfNeeded() {
         if (mSearchResultsFragment != null) {
             return;
@@ -1347,10 +1365,12 @@
         mSearchMenuItemExpanded = true;
     }
 
+    @Deprecated
     public void needToRevertToInitialFragment() {
         mNeedToRevertToInitialFragment = true;
     }
 
+    @Deprecated
     private void revertToInitialFragment() {
         mNeedToRevertToInitialFragment = false;
         mSearchResultsFragment = null;
diff --git a/src/com/android/settings/overlay/FeatureFactory.java b/src/com/android/settings/overlay/FeatureFactory.java
index 55ea4bb..75f1001 100644
--- a/src/com/android/settings/overlay/FeatureFactory.java
+++ b/src/com/android/settings/overlay/FeatureFactory.java
@@ -27,6 +27,7 @@
 import com.android.settings.enterprise.EnterprisePrivacyFeatureProvider;
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProvider;
+import com.android.settings.search2.SearchFeatureProvider;
 
 /**
  * Abstract class for creating feature controllers. Allows OEM implementations to define their own
@@ -80,6 +81,8 @@
     public abstract EnterprisePrivacyFeatureProvider getEnterprisePrivacyFeatureProvider(
             Context context);
 
+    public abstract SearchFeatureProvider getSearchFeatureProvider(Context context);
+
     public static final class FactoryNotFoundException extends RuntimeException {
         public FactoryNotFoundException(Throwable throwable) {
             super("Unable to create factory. Did you misconfigure Proguard?", throwable);
diff --git a/src/com/android/settings/overlay/FeatureFactoryImpl.java b/src/com/android/settings/overlay/FeatureFactoryImpl.java
index ec0ff46..4a7396e 100644
--- a/src/com/android/settings/overlay/FeatureFactoryImpl.java
+++ b/src/com/android/settings/overlay/FeatureFactoryImpl.java
@@ -32,6 +32,8 @@
 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProvider;
 import com.android.settings.localepicker.LocaleFeatureProviderImpl;
+import com.android.settings.search2.SearchFeatureProvider;
+import com.android.settings.search2.SearchFeatureProviderImpl;
 
 /**
  * {@link FeatureFactory} implementation for AOSP Settings.
@@ -44,6 +46,7 @@
     private DashboardFeatureProviderImpl mDashboardFeatureProvider;
     private LocaleFeatureProvider mLocaleFeatureProvider;
     private EnterprisePrivacyFeatureProvider mEnterprisePrivacyFeatureProvider;
+    private SearchFeatureProvider mSearchFeatureProvider;
 
     @Override
     public SupportFeatureProvider getSupportFeatureProvider(Context context) {
@@ -91,9 +94,17 @@
     public EnterprisePrivacyFeatureProvider getEnterprisePrivacyFeatureProvider(Context context) {
         if (mEnterprisePrivacyFeatureProvider == null) {
             mEnterprisePrivacyFeatureProvider = new EnterprisePrivacyFeatureProviderImpl(context,
-                    new DevicePolicyManagerWrapperImpl((DevicePolicyManager)context
+                    new DevicePolicyManagerWrapperImpl((DevicePolicyManager) context
                             .getSystemService(Context.DEVICE_POLICY_SERVICE)));
         }
         return mEnterprisePrivacyFeatureProvider;
     }
+
+    @Override
+    public SearchFeatureProvider getSearchFeatureProvider(Context context) {
+        if (mSearchFeatureProvider == null) {
+            mSearchFeatureProvider = new SearchFeatureProviderImpl(context);
+        }
+        return mSearchFeatureProvider;
+    }
 }
diff --git a/src/com/android/settings/search2/DatabaseResultLoader.java b/src/com/android/settings/search2/DatabaseResultLoader.java
new file mode 100644
index 0000000..aca94b1
--- /dev/null
+++ b/src/com/android/settings/search2/DatabaseResultLoader.java
@@ -0,0 +1,124 @@
+/*
+ * 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 com.android.settings.search2;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.VisibleForTesting;
+import com.android.settings.search.Index;
+import com.android.settings.search.IndexDatabaseHelper;
+import com.android.settings.utils.AsyncLoader;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_ICON_RESID;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_RAW_SUMMARY_ON;
+import static android.provider.SearchIndexablesContract.COLUMN_INDEX_XML_RES_RANK;
+
+
+/**
+ * AsyncTask to retrieve Settings, First party app and any intent based results.
+ */
+public class DatabaseResultLoader extends AsyncLoader<List<SearchResult>> {
+    private final String mQueryText;
+    private final Context mContext;
+    protected final SQLiteDatabase mDatabase;
+
+    public DatabaseResultLoader(Context context, String queryText) {
+        super(context);
+        mDatabase = IndexDatabaseHelper.getInstance(context).getReadableDatabase();
+        mQueryText = queryText;
+        mContext = context;
+    }
+
+    @Override
+    protected void onDiscardResult(List<SearchResult> result) {
+        // TODO Search
+    }
+
+    @Override
+    public List<SearchResult> loadInBackground() {
+        if (mQueryText == null || mQueryText.isEmpty()) {
+            return null;
+        }
+
+        String query = getSQLQuery();
+        Cursor result  = mDatabase.rawQuery(query, null);
+
+        return parseCursorForSearch(result);
+    }
+
+    @Override
+    protected boolean onCancelLoad() {
+        // TODO
+        return super.onCancelLoad();
+    }
+
+    protected String getSQLQuery() {
+        return String.format("SELECT data_rank, data_title, data_summary_on, " +
+                "data_summary_off, data_entries, data_keywords, class_name, screen_title, icon, " +
+                "intent_action, intent_target_package, intent_target_class, enabled, " +
+                "data_key_reference FROM prefs_index WHERE prefs_index MATCH 'data_title:%s* " +
+                "OR data_title_normalized:%s* OR data_keywords:%s*' AND locale = 'en_US'",
+                mQueryText, mQueryText, mQueryText);
+    }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    public ArrayList<SearchResult> parseCursorForSearch(Cursor cursorResults) {
+        if (cursorResults == null) {
+            return null;
+        }
+        final ArrayList<SearchResult> results = new ArrayList<>();
+
+        while (cursorResults.moveToNext()) {
+            final String title = cursorResults.getString(Index.COLUMN_INDEX_TITLE);
+            final String summaryOn = cursorResults.getString(COLUMN_INDEX_RAW_SUMMARY_ON);
+            final ArrayList<String> breadcrumbs = new ArrayList<>();
+            final int rank = cursorResults.getInt(COLUMN_INDEX_XML_RES_RANK);
+
+            final String intentString = cursorResults.getString(Index.COLUMN_INDEX_INTENT_ACTION);
+            final IntentPayload intentPayload = new IntentPayload(new Intent(intentString));
+            final int iconID = cursorResults.getInt(COLUMN_INDEX_RAW_ICON_RESID);
+            Drawable icon;
+            try {
+                icon = mContext.getDrawable(iconID);
+            } catch (Resources.NotFoundException nfe) {
+                icon = mContext.getDrawable(R.drawable.ic_search_history);
+            }
+
+
+            SearchResult.Builder builder = new SearchResult.Builder();
+            builder.addTitle(title)
+                    .addSummary(summaryOn)
+                    .addBreadcrumbs(breadcrumbs)
+                    .addRank(rank)
+                    .addIcon(icon)
+                    .addPayload(intentPayload);
+            results.add(builder.build());
+        }
+        Collections.sort(results);
+        return results;
+    }
+
+}
diff --git a/src/com/android/settings/search2/InlineSliderPayload.java b/src/com/android/settings/search2/InlineSliderPayload.java
new file mode 100644
index 0000000..8f08d59
--- /dev/null
+++ b/src/com/android/settings/search2/InlineSliderPayload.java
@@ -0,0 +1,62 @@
+/*
+ * 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 com.android.settings.search2;
+
+import android.net.Uri;
+import android.os.Parcel;
+
+/**
+ * Payload for Inline Settings results represented by a Slider.
+ */
+public class InlineSliderPayload extends ResultPayload {
+    public final Uri uri;
+
+    private InlineSliderPayload(Parcel in) {
+        uri = in.readParcelable(InlineSliderPayload.class.getClassLoader());
+    }
+
+    public InlineSliderPayload(Uri newUri) {
+        uri = newUri;
+    }
+
+    @Override
+    public int getType() {
+        return PayloadType.INLINE_SLIDER;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(uri, flags);
+    }
+
+    public static final Creator<InlineSliderPayload> CREATOR = new Creator<InlineSliderPayload>() {
+        @Override
+        public InlineSliderPayload createFromParcel(Parcel in) {
+            return new InlineSliderPayload(in);
+        }
+
+        @Override
+        public InlineSliderPayload[] newArray(int size) {
+            return new InlineSliderPayload[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/src/com/android/settings/search2/IntentPayload.java b/src/com/android/settings/search2/IntentPayload.java
new file mode 100644
index 0000000..1ef3797
--- /dev/null
+++ b/src/com/android/settings/search2/IntentPayload.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 com.android.settings.search2;
+
+import android.content.Intent;
+import android.os.Parcel;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Encapsulates the standard intent based results as seen in first party apps and Settings results.
+ */
+public class IntentPayload extends ResultPayload {
+    public final Intent intent;
+
+    private IntentPayload(Parcel in) {
+        intent = in.readParcelable(IntentPayload.class.getClassLoader());
+    }
+
+    public IntentPayload(Intent newIntent) {
+        intent = newIntent;
+    }
+
+    @ResultPayload.PayloadType public int getType() {
+        return PayloadType.INTENT;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeParcelable(intent, flags);
+    }
+
+    public static final Creator<IntentPayload> CREATOR = new Creator<IntentPayload>() {
+        @Override
+        public IntentPayload createFromParcel(Parcel in) {
+            return new IntentPayload(in);
+        }
+
+        @Override
+        public IntentPayload[] newArray(int size) {
+            return new IntentPayload[size];
+        }
+    };
+
+}
\ No newline at end of file
diff --git a/src/com/android/settings/search2/IntentSearchViewHolder.java b/src/com/android/settings/search2/IntentSearchViewHolder.java
new file mode 100644
index 0000000..0b99d6e
--- /dev/null
+++ b/src/com/android/settings/search2/IntentSearchViewHolder.java
@@ -0,0 +1,44 @@
+/*
+ * 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 com.android.settings.search2;
+
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.settings.R;
+
+/**
+ * ViewHolder for intent based search results.
+ * The DatabaseResultLoader is the primary use case for this ViewHolder.
+ */
+public class IntentSearchViewHolder extends SearchViewHolder {
+    public final TextView titleView;
+    public final TextView summaryView;
+    public final ImageView iconView;
+
+    public IntentSearchViewHolder(View view) {
+        super(view);
+        titleView = (TextView) view.findViewById(R.id.title);
+        summaryView = (TextView) view.findViewById(R.id.summary);
+        iconView= (ImageView) view.findViewById(R.id.icon);
+    }
+
+    public void onBind(SearchResult result) {
+        titleView.setText(result.title);
+        summaryView.setText(result.summary);
+        iconView.setImageDrawable(result.icon);
+    }
+}
diff --git a/src/com/android/settings/search2/ResultPayload.java b/src/com/android/settings/search2/ResultPayload.java
new file mode 100644
index 0000000..3a4e477
--- /dev/null
+++ b/src/com/android/settings/search2/ResultPayload.java
@@ -0,0 +1,40 @@
+/*
+ * 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 com.android.settings.search2;
+
+import android.annotation.IntDef;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A interface for search results types. Examples include Inline results, third party apps
+ * or any future possibilities.
+ */
+public abstract class ResultPayload implements Parcelable {
+
+    @IntDef({PayloadType.INLINE_SLIDER, PayloadType.INLINE_SWITCH, PayloadType.INTENT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PayloadType {
+        int INTENT = 0;
+        int INLINE_SLIDER = 1;
+        int INLINE_SWITCH = 2;
+    }
+
+    @ResultPayload.PayloadType public abstract int getType();
+}
diff --git a/src/com/android/settings/search2/SearchActivity.java b/src/com/android/settings/search2/SearchActivity.java
new file mode 100644
index 0000000..25a54cf
--- /dev/null
+++ b/src/com/android/settings/search2/SearchActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.settings.search2;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.os.Bundle;
+
+import com.android.settings.R;
+
+public class SearchActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.search_main);
+
+        FragmentManager fragmentManager = getFragmentManager();
+        Fragment fragment = fragmentManager.findFragmentById(R.id.main_content);
+        if (fragment == null) {
+            fragmentManager.beginTransaction()
+                    .add(R.id.main_content, new SearchFragment())
+                    .commit();
+        }
+    }
+}
diff --git a/src/com/android/settings/search2/SearchFeatureProvider.java b/src/com/android/settings/search2/SearchFeatureProvider.java
new file mode 100644
index 0000000..14f5d13
--- /dev/null
+++ b/src/com/android/settings/search2/SearchFeatureProvider.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.android.settings.search2;
+
+import android.app.Activity;
+import android.widget.SearchView;
+import android.view.Menu;
+
+/**
+ * FeatureProvider for Settings Search
+ */
+public interface SearchFeatureProvider {
+
+    /**
+     * @return true to use the new version of search
+     */
+    boolean isEnabled();
+
+    /**
+     * Inserts the Menu items into Settings activity.
+     * @param menu Items will be inserted into this menu.
+     * @param activity The activity that precedes SearchActivity.
+     */
+    void setUpSearchMenu(Menu menu, Activity activity);
+}
diff --git a/src/com/android/settings/search2/SearchFeatureProviderImpl.java b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
new file mode 100644
index 0000000..3c6dc35
--- /dev/null
+++ b/src/com/android/settings/search2/SearchFeatureProviderImpl.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 com.android.settings.search2;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.widget.SearchView;
+import android.view.Menu;
+
+import android.view.MenuItem;
+import com.android.settings.R;
+
+/**
+ * FeatureProvider for the refactored search code.
+ */
+public class SearchFeatureProviderImpl implements SearchFeatureProvider {
+    protected Context mContext;
+
+
+    public SearchFeatureProviderImpl(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return false;
+    }
+
+    @Override
+    public void setUpSearchMenu(Menu menu, final Activity activity) {
+        if (menu == null || activity == null) {
+            return;
+        }
+        String menuTitle = mContext.getString(R.string.search_menu);
+        MenuItem menuItem = menu.add(Menu.NONE, Menu.NONE, Menu.NONE, menuTitle)
+            .setIcon(R.drawable.abc_ic_search_api_material)
+            .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+                @Override
+                public boolean onMenuItemClick(MenuItem item) {
+                    Intent intent = new Intent(activity, SearchActivity.class);
+                    activity.startActivity(intent);
+                    return true;
+                }
+            });
+
+        menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+    }
+}
diff --git a/src/com/android/settings/search2/SearchFragment.java b/src/com/android/settings/search2/SearchFragment.java
new file mode 100644
index 0000000..18f20be
--- /dev/null
+++ b/src/com/android/settings/search2/SearchFragment.java
@@ -0,0 +1,158 @@
+/*
+ * 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 com.android.settings.search2;
+
+import android.app.Activity;
+import android.content.Loader;
+import android.os.Bundle;
+import android.app.LoaderManager;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.widget.SearchView;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
+import com.android.settings.core.InstrumentedFragment;
+
+import java.util.List;
+
+public class SearchFragment extends InstrumentedFragment implements
+        SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener,
+        LoaderManager.LoaderCallbacks<List<SearchResult>>  {
+
+    private static final int DATABASE_LOADER_ID = 0;
+
+    private SearchResultsAdapter mSearchAdapter;
+
+    private DatabaseResultLoader mSearchLoader;
+
+    private RecyclerView mResultsRecyclerView;
+    private SearchView mSearchView;
+    private MenuItem mSearchMenuItem;
+
+    private String mQuery;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setHasOptionsMenu(true);
+
+        mSearchAdapter = new SearchResultsAdapter();
+
+        final LoaderManager loaderManager = getLoaderManager();
+        loaderManager.initLoader(DATABASE_LOADER_ID, null, this);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        final View view = inflater.inflate(R.layout.search_panel_2, container, false);
+        mResultsRecyclerView = (RecyclerView) view.findViewById(R.id.list_results);
+
+        mResultsRecyclerView.setAdapter(mSearchAdapter);
+        mResultsRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
+        return view;
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        super.onCreateOptionsMenu(menu, inflater);
+        inflater.inflate(R.menu.search_options_menu, menu);
+
+
+        mSearchMenuItem = menu.findItem(R.id.search);
+
+        mSearchView = (SearchView) mSearchMenuItem.getActionView();
+        mSearchView.setOnQueryTextListener(this);
+        mSearchView.setMaxWidth(Integer.MAX_VALUE);
+        mSearchMenuItem.expandActionView();
+    }
+
+    @Override
+    public boolean onMenuItemActionExpand(MenuItem item) {
+        return true;
+    }
+
+    @Override
+    public boolean onMenuItemActionCollapse(MenuItem item) {
+        // Return false to prevent the search box from collapsing.
+        return false;
+    }
+
+    @Override
+    public boolean onQueryTextChange(String query) {
+        if (query == null || query.equals(mQuery)) {
+            return false;
+        }
+
+        mQuery = query;
+        clearLoaders();
+
+        final LoaderManager loaderManager = getLoaderManager();
+        loaderManager.restartLoader(DATABASE_LOADER_ID, null, this);
+
+        return true;
+    }
+
+    @Override
+    public boolean onQueryTextSubmit(String query) {
+        return false;
+    }
+
+    @Override
+    public Loader<List<SearchResult>> onCreateLoader(int id, Bundle args) {
+        final Activity activity = getActivity();
+
+        switch (id) {
+            case DATABASE_LOADER_ID:
+                mSearchLoader = new DatabaseResultLoader(activity, mQuery);
+                return mSearchLoader;
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public void onLoadFinished(Loader<List<SearchResult>> loader, List<SearchResult> data) {
+        if (data == null) {
+            return;
+        }
+
+        mSearchAdapter.mergeResults(data, loader.getClass().getName());
+    }
+
+    @Override
+    public void onLoaderReset(Loader<List<SearchResult>> loader) { }
+
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS;
+    }
+
+    private void clearLoaders() {
+        if (mSearchLoader != null) {
+            mSearchLoader.cancelLoad();
+            mSearchLoader = null;
+        }
+    }
+}
diff --git a/src/com/android/settings/search2/SearchResult.java b/src/com/android/settings/search2/SearchResult.java
new file mode 100644
index 0000000..e483df3
--- /dev/null
+++ b/src/com/android/settings/search2/SearchResult.java
@@ -0,0 +1,146 @@
+/*
+ * 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 com.android.settings.search2;
+
+import android.graphics.drawable.Drawable;
+
+import java.util.ArrayList;
+
+/**
+ * Dataclass as an interface for all Search Results.
+ */
+public class SearchResult implements Comparable<SearchResult> {
+    @Override
+    public int compareTo(SearchResult searchResult) {
+        if (searchResult == null) {
+            return -1;
+        }
+        return this.rank - searchResult.rank;
+    }
+
+    public static class Builder {
+        protected String mTitle;
+        protected String mSummary;
+        protected ArrayList<String> mBreadcrumbs;
+        protected int mRank = -1;
+        protected ResultPayload mResultPayload;
+        protected Drawable mIcon;
+
+        public Builder addTitle(String title) {
+            mTitle = title;
+            return this;
+        }
+
+        public Builder addSummary(String summary) {
+            mSummary = summary;
+            return this;
+        }
+
+        public Builder addBreadcrumbs(ArrayList<String> breadcrumbs) {
+            mBreadcrumbs = breadcrumbs;
+            return this;
+        }
+
+        public Builder addRank(int rank) {
+            if (rank < 0 || rank > 9) {
+                rank = 42;
+            }
+            mRank = rank;
+            return this;
+        }
+
+        public Builder addIcon(Drawable icon) {
+            mIcon = icon;
+            return this;
+        }
+
+        public Builder addPayload(ResultPayload payload) {
+            mResultPayload = payload;
+            return this;
+        }
+
+        public SearchResult build() {
+            // Check that all of the mandatory fields are set.
+            if (mTitle == null) {
+                throw new IllegalArgumentException("SearchResult missing title argument");
+            } else if (mSummary == null ) {
+                throw new IllegalArgumentException("SearchResult missing summary argument");
+            } else if (mBreadcrumbs == null){
+                throw new IllegalArgumentException("SearchResult missing breadcrumbs argument");
+            } else if (mRank == -1) {
+                throw new IllegalArgumentException("SearchResult missing rank argument");
+            } else if (mIcon == null) {
+                throw new IllegalArgumentException("SearchResult missing icon argument");
+            } else if (mResultPayload == null) {
+                throw new IllegalArgumentException("SearchResult missing Payload argument");
+            }
+            return new SearchResult(this);
+        }
+    }
+
+    /**
+     * The title of the result and main text displayed.
+     * Intent Results: Displays as the primary
+     */
+    public final String title;
+
+    /**
+     * Summary / subtitle text
+     * Intent Results: Displays the text underneath the title
+     */
+    final public String summary;
+
+    /**
+     * An ordered list of the information hierarchy.
+     * Intent Results: Displayed a hierarchy of selections to reach the setting from the home screen
+     */
+    public final ArrayList<String> breadcrumbs;
+
+    /**
+     * A suggestion for the ranking of the result.
+     * Based on Settings Rank:
+     * 1 is a near perfect match
+     * 9 is the weakest match
+     * TODO subject to change
+     */
+    public final int rank;
+
+    /**
+     * Identifier for the recycler view adapter.
+     */
+    @ResultPayload.PayloadType public final int viewType;
+
+    /**
+     * Metadata for the specific result types.
+     */
+    public final ResultPayload payload;
+
+    /**
+     * Result's icon.
+     */
+    public final Drawable icon;
+
+    private SearchResult(Builder builder) {
+        title = builder.mTitle;
+        summary = builder.mSummary;
+        breadcrumbs = builder.mBreadcrumbs;
+        rank = builder.mRank;
+        icon = builder.mIcon;
+        payload = builder.mResultPayload;
+        viewType = payload.getType();
+    }
+}
diff --git a/src/com/android/settings/search2/SearchResultsAdapter.java b/src/com/android/settings/search2/SearchResultsAdapter.java
new file mode 100644
index 0000000..22f106b
--- /dev/null
+++ b/src/com/android/settings/search2/SearchResultsAdapter.java
@@ -0,0 +1,101 @@
+/*
+ * 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 com.android.settings.search2;
+
+import android.support.annotation.VisibleForTesting;
+import android.support.v7.widget.RecyclerView.Adapter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.settings.R;
+import com.android.settings.search2.ResultPayload.PayloadType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class SearchResultsAdapter extends Adapter<SearchViewHolder> {
+    private ArrayList<SearchResult> mSearchResults;
+    private HashMap<String, List<SearchResult>> mResultsMap;
+
+    public SearchResultsAdapter() {
+        mSearchResults = new ArrayList<>();
+        mResultsMap = new HashMap<>();
+
+        setHasStableIds(true);
+    }
+
+    public void mergeResults(List<SearchResult> freshResults, String loaderClassName) {
+        if (freshResults == null) {
+            return;
+        }
+        mResultsMap.put(loaderClassName, freshResults);
+        mSearchResults = mergeMappedResults();
+        notifyDataSetChanged();
+    }
+
+    private ArrayList<SearchResult> mergeMappedResults() {
+        ArrayList<SearchResult> mergedResults = new ArrayList<>();
+        for(String key : mResultsMap.keySet()) {
+            mergedResults.addAll(mResultsMap.get(key));
+        }
+        return mergedResults;
+    }
+
+    @Override
+    public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+        switch(viewType) {
+            case PayloadType.INTENT:
+                View view = inflater.inflate(R.layout.search_intent_item, parent, false);
+                return new IntentSearchViewHolder(view);
+            case PayloadType.INLINE_SLIDER:
+                return null;
+            case PayloadType.INLINE_SWITCH:
+                return null;
+            default:
+                return null;
+        }
+    }
+
+    @Override
+    public void onBindViewHolder(SearchViewHolder holder, int position) {
+        SearchResult result = mSearchResults.get(position);
+        holder.onBind(result);
+    }
+
+    @Override
+    public long getItemId(int position) {
+        return super.getItemId(position);
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        return mSearchResults.get(position).viewType;
+    }
+
+    @Override
+    public int getItemCount() {
+        return mSearchResults.size();
+    }
+
+    @VisibleForTesting
+    public ArrayList<SearchResult> getSearchResults() {
+        return mSearchResults;
+    }
+}
diff --git a/src/com/android/settings/search2/SearchViewHolder.java b/src/com/android/settings/search2/SearchViewHolder.java
new file mode 100644
index 0000000..2f500fb
--- /dev/null
+++ b/src/com/android/settings/search2/SearchViewHolder.java
@@ -0,0 +1,33 @@
+/*
+ * 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 com.android.settings.search2;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+
+/**
+ * The ViewHolder for the Search RecyclerView.
+ * There are multiple search result types in the same Recycler view with different UI requirements.
+ * Some examples include Intent results, Inline results, and Help articles.
+ */
+public abstract class SearchViewHolder extends RecyclerView.ViewHolder {
+
+    public SearchViewHolder(View view) {
+        super(view);
+    }
+
+    public abstract void onBind(SearchResult result);
+}
\ No newline at end of file