Merge "Clean up search fragment loader lifecycle."
diff --git a/res/menu/search_options_menu.xml b/res/menu/search_options_menu.xml
deleted file mode 100644
index 25a79d4..0000000
--- a/res/menu/search_options_menu.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2016 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:id="@+id/search"
-        android:title="@string/search_menu"
-        android:icon="@*android:drawable/ic_search_api_material"
-        android:showAsAction="collapseActionView|ifRoom"
-        android:actionViewClass="android.widget.SearchView"/>
-</menu>
diff --git a/src/com/android/settings/dashboard/DashboardContainerFragment.java b/src/com/android/settings/dashboard/DashboardContainerFragment.java
index 45c423f..b3ee808 100644
--- a/src/com/android/settings/dashboard/DashboardContainerFragment.java
+++ b/src/com/android/settings/dashboard/DashboardContainerFragment.java
@@ -28,9 +28,9 @@
 import android.view.ViewGroup;
 
 import com.android.internal.logging.nano.MetricsProto;
-import com.android.settings.core.InstrumentedFragment;
 import com.android.settings.R;
 import com.android.settings.SettingsActivity;
+import com.android.settings.core.InstrumentedFragment;
 import com.android.settings.core.instrumentation.MetricsFeatureProvider;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.overlay.SupportFeatureProvider;
@@ -77,7 +77,7 @@
 
         // check if support tab needs to be selected
         final String selectedTab = getArguments().
-            getString(EXTRA_SELECT_SETTINGS_TAB, ARG_SUMMARY_TAB);
+                getString(EXTRA_SELECT_SETTINGS_TAB, ARG_SUMMARY_TAB);
         if (TextUtils.equals(selectedTab, ARG_SUPPORT_TAB)) {
             mViewPager.setCurrentItem(INDEX_SUPPORT_FRAGMENT);
         } else {
diff --git a/src/com/android/settings/dashboard/DashboardSummary.java b/src/com/android/settings/dashboard/DashboardSummary.java
index b98de22..136ccaf 100644
--- a/src/com/android/settings/dashboard/DashboardSummary.java
+++ b/src/com/android/settings/dashboard/DashboardSummary.java
@@ -22,7 +22,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.SimpleItemAnimator;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
diff --git a/src/com/android/settings/search2/SearchFeatureProvider.java b/src/com/android/settings/search2/SearchFeatureProvider.java
index 14f5d13..569a627 100644
--- a/src/com/android/settings/search2/SearchFeatureProvider.java
+++ b/src/com/android/settings/search2/SearchFeatureProvider.java
@@ -16,7 +16,7 @@
 package com.android.settings.search2;
 
 import android.app.Activity;
-import android.widget.SearchView;
+import android.content.Context;
 import android.view.Menu;
 
 /**
@@ -31,8 +31,14 @@
 
     /**
      * 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);
+
+    /**
+     * Returns a new loader to search in index database.
+     */
+    DatabaseResultLoader getDatabaseSearchLoader(Context context, String query);
 }
diff --git a/src/com/android/settings/search2/SearchFeatureProviderImpl.java b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
index 3c6dc35..81a41dc 100644
--- a/src/com/android/settings/search2/SearchFeatureProviderImpl.java
+++ b/src/com/android/settings/search2/SearchFeatureProviderImpl.java
@@ -24,6 +24,9 @@
 
 import android.view.MenuItem;
 import com.android.settings.R;
+import com.android.settings.utils.AsyncLoader;
+
+import java.util.List;
 
 /**
  * FeatureProvider for the refactored search code.
@@ -60,4 +63,9 @@
 
         menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
     }
+
+    @Override
+    public DatabaseResultLoader getDatabaseSearchLoader(Context context, String query) {
+        return new DatabaseResultLoader(context, query);
+    }
 }
diff --git a/src/com/android/settings/search2/SearchFragment.java b/src/com/android/settings/search2/SearchFragment.java
index 18f20be..1fb123c 100644
--- a/src/com/android/settings/search2/SearchFragment.java
+++ b/src/com/android/settings/search2/SearchFragment.java
@@ -16,107 +16,110 @@
 
 package com.android.settings.search2;
 
+import android.app.ActionBar;
 import android.app.Activity;
+import android.app.LoaderManager;
+import android.content.Context;
 import android.content.Loader;
 import android.os.Bundle;
-import android.app.LoaderManager;
+import android.support.annotation.VisibleForTesting;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
+import android.text.TextUtils;
 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.LinearLayout.LayoutParams;
 import android.widget.SearchView;
 
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.settings.R;
 import com.android.settings.core.InstrumentedFragment;
+import com.android.settings.overlay.FeatureFactory;
 
 import java.util.List;
 
 public class SearchFragment extends InstrumentedFragment implements
-        SearchView.OnQueryTextListener, MenuItem.OnActionExpandListener,
-        LoaderManager.LoaderCallbacks<List<SearchResult>>  {
+        SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks<List<SearchResult>> {
 
+    // State values
+    static final String STATE_QUERY = "query";
+
+    // Loader IDs
     private static final int DATABASE_LOADER_ID = 0;
 
-    private SearchResultsAdapter mSearchAdapter;
+    @VisibleForTesting
+    String mQuery;
 
+    private SearchFeatureProvider mSearchFeatureProvider;
     private DatabaseResultLoader mSearchLoader;
 
+    private SearchResultsAdapter mSearchAdapter;
     private RecyclerView mResultsRecyclerView;
-    private SearchView mSearchView;
-    private MenuItem mSearchMenuItem;
 
-    private String mQuery;
+    @Override
+    public int getMetricsCategory() {
+        return MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mSearchFeatureProvider = FeatureFactory.getFactory(context)
+                .getSearchFeatureProvider(context);
+    }
 
     @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);
+        if (savedInstanceState != null) {
+            mQuery = savedInstanceState.getString(STATE_QUERY);
+            getLoaderManager().initLoader(DATABASE_LOADER_ID, null, this);
+        }
+        final ActionBar actionBar = getActivity().getActionBar();
+        actionBar.setCustomView(makeSearchView(actionBar, mQuery));
+        actionBar.setDisplayShowCustomEnabled(true);
+        actionBar.setDisplayShowTitleEnabled(false);
     }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                             Bundle savedInstanceState) {
+            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;
+    public void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putString(STATE_QUERY, mQuery);
     }
 
     @Override
     public boolean onQueryTextChange(String query) {
-        if (query == null || query.equals(mQuery)) {
-            return false;
+        if (TextUtils.equals(query, mQuery)) {
+            return true;
         }
-
         mQuery = query;
-        clearLoaders();
+        mSearchAdapter.clearResults();
 
-        final LoaderManager loaderManager = getLoaderManager();
-        loaderManager.restartLoader(DATABASE_LOADER_ID, null, this);
+        if (TextUtils.isEmpty(mQuery)) {
+            getLoaderManager().destroyLoader(DATABASE_LOADER_ID);
+        } else {
+            restartLoaders();
+        }
 
         return true;
     }
 
     @Override
     public boolean onQueryTextSubmit(String query) {
-        return false;
+        return true;
     }
 
     @Override
@@ -125,7 +128,7 @@
 
         switch (id) {
             case DATABASE_LOADER_ID:
-                mSearchLoader = new DatabaseResultLoader(activity, mQuery);
+                mSearchLoader = mSearchFeatureProvider.getDatabaseSearchLoader(activity, mQuery);
                 return mSearchLoader;
             default:
                 return null;
@@ -142,17 +145,22 @@
     }
 
     @Override
-    public void onLoaderReset(Loader<List<SearchResult>> loader) { }
-
-    @Override
-    public int getMetricsCategory() {
-        return MetricsProto.MetricsEvent.DASHBOARD_SEARCH_RESULTS;
+    public void onLoaderReset(Loader<List<SearchResult>> loader) {
     }
 
-    private void clearLoaders() {
-        if (mSearchLoader != null) {
-            mSearchLoader.cancelLoad();
-            mSearchLoader = null;
-        }
+    private void restartLoaders() {
+        final LoaderManager loaderManager = getLoaderManager();
+        loaderManager.restartLoader(DATABASE_LOADER_ID, null /* args */, this /* callback */);
+    }
+
+    private SearchView makeSearchView(ActionBar actionBar, String query) {
+        final SearchView searchView = new SearchView(actionBar.getThemedContext());
+        searchView.setIconifiedByDefault(false);
+        searchView.setQuery(query, false /* submitQuery */);
+        searchView.setOnQueryTextListener(this);
+        final LayoutParams lp =
+                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
+        searchView.setLayoutParams(lp);
+        return searchView;
     }
 }
diff --git a/src/com/android/settings/search2/SearchResultsAdapter.java b/src/com/android/settings/search2/SearchResultsAdapter.java
index 22f106b..62b79b3 100644
--- a/src/com/android/settings/search2/SearchResultsAdapter.java
+++ b/src/com/android/settings/search2/SearchResultsAdapter.java
@@ -28,10 +28,11 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 public class SearchResultsAdapter extends Adapter<SearchViewHolder> {
-    private ArrayList<SearchResult> mSearchResults;
-    private HashMap<String, List<SearchResult>> mResultsMap;
+    private final List<SearchResult> mSearchResults;
+    private final Map<String, List<SearchResult>> mResultsMap;
 
     public SearchResultsAdapter() {
         mSearchResults = new ArrayList<>();
@@ -45,13 +46,19 @@
             return;
         }
         mResultsMap.put(loaderClassName, freshResults);
-        mSearchResults = mergeMappedResults();
+        mSearchResults.addAll(mergeMappedResults());
+        notifyDataSetChanged();
+    }
+
+    public void clearResults() {
+        mSearchResults.clear();
+        mResultsMap.clear();
         notifyDataSetChanged();
     }
 
     private ArrayList<SearchResult> mergeMappedResults() {
         ArrayList<SearchResult> mergedResults = new ArrayList<>();
-        for(String key : mResultsMap.keySet()) {
+        for (String key : mResultsMap.keySet()) {
             mergedResults.addAll(mResultsMap.get(key));
         }
         return mergedResults;
@@ -60,7 +67,7 @@
     @Override
     public SearchViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         LayoutInflater inflater = LayoutInflater.from(parent.getContext());
-        switch(viewType) {
+        switch (viewType) {
             case PayloadType.INTENT:
                 View view = inflater.inflate(R.layout.search_intent_item, parent, false);
                 return new IntentSearchViewHolder(view);
@@ -95,7 +102,7 @@
     }
 
     @VisibleForTesting
-    public ArrayList<SearchResult> getSearchResults() {
+    public List<SearchResult> getSearchResults() {
         return mSearchResults;
     }
 }
diff --git a/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java b/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java
index b3da4eb..81e9180 100644
--- a/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java
+++ b/tests/robotests/src/com/android/settings/search/SearchAdapterTest.java
@@ -20,20 +20,25 @@
 import android.app.Activity;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+
+import com.android.settings.R;
 import com.android.settings.SettingsRobolectricTestRunner;
 import com.android.settings.TestConfig;
-import com.android.settings.search2.*;
+import com.android.settings.search2.DatabaseResultLoader;
+import com.android.settings.search2.IntentPayload;
+import com.android.settings.search2.ResultPayload;
+import com.android.settings.search2.SearchResult;
 import com.android.settings.search2.SearchResult.Builder;
-import com.android.settings.R;
-
-import java.util.ArrayList;
+import com.android.settings.search2.SearchResultsAdapter;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-
-import org.robolectric.annotation.Config;
 import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -78,7 +83,7 @@
 
     @Test
     public void testNoResultsAdded_EmptyListReturned() {
-        ArrayList<SearchResult> updatedResults = mAdapter.getSearchResults();
+        List<SearchResult> updatedResults = mAdapter.getSearchResults();
         assertThat(updatedResults).isEmpty();
     }
 
@@ -88,7 +93,7 @@
         ArrayList<SearchResult> intentResults = getIntentSampleResults();
         mAdapter.mergeResults(intentResults, mLoaderClassName);
 
-        ArrayList<SearchResult> updatedResults = mAdapter.getSearchResults();
+        List<SearchResult> updatedResults = mAdapter.getSearchResults();
         assertThat(updatedResults).containsAllIn(intentResults);
     }
 
@@ -98,7 +103,7 @@
         mAdapter.mergeResults(intentResults, mLoaderClassName);
         mAdapter.mergeResults(intentResults, mLoaderClassName);
 
-        ArrayList<SearchResult> updatedResults = mAdapter.getSearchResults();
+        List<SearchResult> updatedResults = mAdapter.getSearchResults();
         assertThat(updatedResults).containsAllIn(intentResults);
     }
 }
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
new file mode 100644
index 0000000..979b7e5
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/search2/SearchFragmentTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.os.Bundle;
+
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.testutils.FakeFeatureFactory;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ActivityController;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SearchFragmentTest {
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+    @Mock
+    private DatabaseResultLoader mDatabaseResultLoader;
+    private FakeFeatureFactory mFeatureFactory;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        FakeFeatureFactory.setupForTest(mContext);
+        mFeatureFactory = (FakeFeatureFactory) FakeFeatureFactory.getFactory(mContext);
+    }
+
+    @Test
+    public void screenRotate_shouldPersistQuery() {
+        when(mFeatureFactory.searchFeatureProvider
+                .getDatabaseSearchLoader(any(Context.class), anyString()))
+                .thenReturn(mDatabaseResultLoader);
+
+        final Bundle bundle = new Bundle();
+        final String testQuery = "test";
+        ActivityController<SearchActivity> activityController =
+                Robolectric.buildActivity(SearchActivity.class);
+        activityController.setup();
+        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
+                .findFragmentById(R.id.main_content);
+
+        fragment.mQuery = testQuery;
+
+        activityController.saveInstanceState(bundle).pause().stop().destroy();
+
+        activityController = Robolectric.buildActivity(SearchActivity.class);
+        activityController.setup(bundle);
+
+        verify(mFeatureFactory.searchFeatureProvider)
+                .getDatabaseSearchLoader(any(Context.class), anyString());
+    }
+
+    @Test
+    public void queryTextChange_shouldTriggerLoader() {
+        when(mFeatureFactory.searchFeatureProvider
+                .getDatabaseSearchLoader(any(Context.class), anyString()))
+                .thenReturn(mDatabaseResultLoader);
+
+        final String testQuery = "test";
+        ActivityController<SearchActivity> activityController =
+                Robolectric.buildActivity(SearchActivity.class);
+        activityController.setup();
+        SearchFragment fragment = (SearchFragment) activityController.get().getFragmentManager()
+                .findFragmentById(R.id.main_content);
+
+        fragment.onQueryTextChange(testQuery);
+
+        verify(mFeatureFactory.searchFeatureProvider)
+                .getDatabaseSearchLoader(any(Context.class), anyString());
+    }
+}