</intent-filter>
</activity>
+ <activity android:name=".search2.SearchActivity"
+ android:label="@string/search_settings"
+ android:icon="@drawable/ic_search_history">
+ </activity>
+
<!-- Top-level settings -->
<activity android:name="Settings$WifiSettingsActivity"
--- /dev/null
+<?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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingEnd="?android:attr/scrollbarSize"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/search_result_item_image_size"
+ android:layout_height="@dimen/search_result_item_image_size"
+ android:layout_marginStart="@dimen/search_result_item_image_margin_start"
+ android:layout_marginEnd="@dimen/search_result_item_image_margin_end"
+ android:scaleType="centerInside"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingTop="10dp"
+ android:orientation="vertical">
+
+ <TextView android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+
+ <TextView android:id="@+id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+
+ </LinearLayout>
+
+</LinearLayout>
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 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.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/main_content"
+ android:layout_height="match_parent"
+ android:layout_width="match_parent"
+ android:background="@color/material_grey_300"/>
--- /dev/null
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/search_panel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:orientation="vertical">
+
+ <LinearLayout android:id="@+id/layout_recent_searches"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:orientation="vertical">
+
+ <!-- Padding is included in the background -->
+ <android.support.v7.widget.RecyclerView android:id="@+id/list_recent_searches"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/dashboard_padding_start"
+ android:paddingEnd="@dimen/dashboard_padding_end"
+ android:paddingTop="@dimen/dashboard_padding_top"
+ android:paddingBottom="@dimen/dashboard_padding_bottom"
+ android:scrollbarStyle="outsideOverlay"
+ android:headerDividersEnabled="false"
+ android:background="@drawable/search_panel_list_background"
+ android:elevation="@dimen/search_panel_elevation"/>
+ </LinearLayout>
+
+ <LinearLayout android:id="@+id/layout_results"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:orientation="vertical">
+
+ <!-- Padding is included in the background -->
+ <android.support.v7.widget.RecyclerView android:id="@+id/list_results"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingStart="@dimen/dashboard_padding_start"
+ android:paddingEnd="@dimen/dashboard_padding_end"
+ android:paddingTop="@dimen/dashboard_padding_top"
+ android:paddingBottom="@dimen/dashboard_padding_bottom"
+ android:scrollbarStyle="outsideOverlay"
+ android:scrollbars="vertical"
+ android:background="@drawable/search_panel_list_background"/>
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
--- /dev/null
+<?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>
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;
private SearchResultsSummary mSearchResultsFragment;
private String mSearchQuery;
+ private SearchFeatureProvider mSearchFeatureProvider;
+
// Categories
private ArrayList<DashboardCategory> mCategories = new ArrayList<DashboardCategory>();
}
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);
mSearchMenuItem.expandActionView();
}
mSearchView.setQuery(query, true /* submit */);
-
return true;
}
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();
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);
}
}
}
+ @Deprecated
private void switchToSearchResultsFragmentIfNeeded() {
if (mSearchResultsFragment != null) {
return;
mSearchMenuItemExpanded = true;
}
+ @Deprecated
public void needToRevertToInitialFragment() {
mNeedToRevertToInitialFragment = true;
}
+ @Deprecated
private void revertToInitialFragment() {
mNeedToRevertToInitialFragment = false;
mSearchResultsFragment = null;
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
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);
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.
private DashboardFeatureProviderImpl mDashboardFeatureProvider;
private LocaleFeatureProvider mLocaleFeatureProvider;
private EnterprisePrivacyFeatureProvider mEnterprisePrivacyFeatureProvider;
+ private SearchFeatureProvider mSearchFeatureProvider;
@Override
public SupportFeatureProvider getSupportFeatureProvider(Context context) {
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;
+ }
}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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
getPackageName(),
Fs.fileFromPath("./frameworks/base/packages/SettingsLib/res"),
null));
+ paths.add(new ResourcePath(
+ getPackageName(),
+ Fs.fileFromPath("./frameworks/base/core/res/res"),
+ null));
return paths;
}
};
--- /dev/null
+/*
+ * 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.search;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.database.MatrixCursor;
+import android.graphics.drawable.Drawable;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search2.DatabaseResultLoader;
+import com.android.settings.search2.IntentPayload;
+import com.android.settings.search2.ResultPayload;
+import com.android.settings.search2.ResultPayload.PayloadType;
+import com.android.settings.search2.SearchResult;
+import com.android.settings.R;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.annotation.Config;
+import org.robolectric.Robolectric;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class DatabaseResultLoaderTest {
+ private DatabaseResultLoader mLoader;
+
+ private static final String[] TITLES = new String[] {"title1", "title2", "title3"};
+ private static final String SUMMARY = "SUMMARY";
+ private static final int EXAMPLES = 3;
+ private static final Intent mIntent = new Intent("com.android.settings");
+ private static final int mIcon = R.drawable.ic_search_history;
+
+ private Drawable mDrawable;
+
+ @Before
+ public void setUp() {
+ Context context = Robolectric.buildActivity(Activity.class).get();
+ mDrawable = context.getDrawable(mIcon);
+ mLoader = new DatabaseResultLoader(context, "");
+ }
+
+ @Test
+ public void testParseNullResults_ReturnsNull() {
+ List<SearchResult> results = mLoader.parseCursorForSearch(null);
+ assertThat(results).isNull();
+ }
+
+ @Test
+ public void testParseCursor_NotNull() {
+ List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+ assertThat(results).isNotNull();
+ }
+
+ @Test
+ public void testParseCursor_MatchesRank() {
+ List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+ for (int i = 0; i < EXAMPLES; i++) {
+ assertThat(results.get(i).rank).isEqualTo(i);
+ }
+ }
+
+ @Test
+ public void testParseCursor_MatchesTitle() {
+ List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+ for (int i = 0; i < EXAMPLES; i++) {
+ assertThat(results.get(i).title).isEqualTo(TITLES[i]);
+ }
+ }
+
+ @Test
+ public void testParseCursor_MatchesSummary() {
+ List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+ for (int i = 0; i < EXAMPLES; i++) {
+ assertThat(results.get(i).summary).isEqualTo(SUMMARY);
+ }
+ }
+
+ @Test
+ public void testParseCursor_MatchesIcon() {
+ List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+ for (int i = 0; i < EXAMPLES; i++) {
+ Drawable resultDrawable = results.get(i).icon;
+ assertThat(resultDrawable.toString()).isEqualTo(mDrawable.toString());
+ }
+ }
+
+ @Test
+ public void testParseCursor_MatchesPayloadType() {
+ List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+ ResultPayload payload;
+ for (int i = 0; i < EXAMPLES; i++) {
+ payload = results.get(i).payload;
+ assertThat(payload.getType()).isEqualTo(PayloadType.INTENT);
+ }
+ }
+
+ @Test
+ public void testParseCursor_MatchesIntentPayload() {
+ List<SearchResult> results = mLoader.parseCursorForSearch(getDummyCursor());
+ IntentPayload payload;
+ for (int i = 0; i < EXAMPLES; i++) {
+ payload = (IntentPayload) results.get(i).payload;
+ Intent intent = payload.intent;
+ assertThat(intent.getAction()).isEqualTo(mIntent.getAction());
+ }
+ }
+
+ private MatrixCursor getDummyCursor() {
+ String[] columns = new String[] {"rank", "title", "summary_on", "summary off", "entries",
+ "keywords", "class name", "screen title", "icon", "intent action",
+ "target package", "target class", "enabled", "key", "user id"};
+ MatrixCursor cursor = new MatrixCursor(columns);
+ final String BLANK = "";
+
+ for (int i = 0; i < EXAMPLES; i++) {
+ ArrayList<String> item = new ArrayList<>(columns.length);
+ item.add(Integer.toString(i));
+ item.add(TITLES[i]);
+ item.add(SUMMARY);
+ item.add(BLANK); // summary off
+ item.add(BLANK); // entries
+ item.add(BLANK); // keywords
+ item.add(BLANK); // classname
+ item.add(BLANK); // screen title
+ item.add(Integer.toString(mIcon));
+ item.add(mIntent.getAction());
+ item.add(BLANK); // target package
+ item.add(BLANK); // target class
+ item.add(BLANK); // enabled
+ item.add(BLANK); // key
+ item.add(BLANK); // user id
+
+ cursor.addRow(item);
+ }
+ return cursor;
+ }
+}
--- /dev/null
+/*
+ * 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.search;
+
+import android.net.Uri;
+import android.os.Parcel;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search2.InlineSliderPayload;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class InlineSliderPayloadTest {
+ private InlineSliderPayload mPayload;
+
+ @Test
+ public void testParcelOrdering_StaysValid() {
+ Uri uri = Uri.parse("http://www.TESTURI.com");
+ Parcel parcel = Parcel.obtain();
+
+ mPayload = new InlineSliderPayload(uri);
+ mPayload.writeToParcel(parcel, 0);
+ // Reset parcel for reading
+ parcel.setDataPosition(0);
+ InlineSliderPayload newPayload = InlineSliderPayload.CREATOR.createFromParcel(parcel);
+
+ String originalUri = mPayload.uri.toString();
+ String copiedUri = newPayload.uri.toString();
+ assertThat(originalUri).isEqualTo(copiedUri);
+ }
+}
--- /dev/null
+/*
+ * 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.search;
+
+import android.content.Intent;
+import android.os.Parcel;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search2.IntentPayload;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.annotation.Config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class IntentPayloadTest {
+ private IntentPayload mPayload;
+
+ private final String EXTRA_KEY = "key";
+ private final String EXTRA_VALUE = "value";
+
+ @Test
+ public void testParcelOrdering_StaysValid() {
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_KEY, EXTRA_VALUE);
+ Parcel parcel = Parcel.obtain();
+
+ mPayload = new IntentPayload(intent);
+ mPayload.writeToParcel(parcel, 0);
+ // Reset parcel for reading
+ parcel.setDataPosition(0);
+ IntentPayload newPayload = IntentPayload.CREATOR.createFromParcel(parcel);
+
+ String originalIntentExtra = mPayload.intent.getStringExtra(EXTRA_KEY);
+ String copiedIntentExtra = newPayload.intent.getStringExtra(EXTRA_KEY);
+ assertThat(originalIntentExtra).isEqualTo(copiedIntentExtra);
+ }
+}
--- /dev/null
+/*
+ * 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.search;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import com.android.settings.R;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search2.IntentPayload;
+import com.android.settings.search2.IntentSearchViewHolder;
+import com.android.settings.search2.SearchResult.Builder;
+import com.android.settings.search2.SearchResult;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import java.util.ArrayList;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class IntentSearchViewHolderTest {
+ private IntentSearchViewHolder mHolder;
+ private static Drawable mIcon;
+
+ private static final String TITLE = "title";
+ private static final String SUMMARY = "summary";
+
+
+ @Before
+ public void setUp() {
+ final Context context = ShadowApplication.getInstance().getApplicationContext();
+ View view = LayoutInflater.from(context).inflate(R.layout.search_intent_item, null);
+ mHolder = new IntentSearchViewHolder(view);
+
+ mIcon = context.getDrawable(R.drawable.ic_search_history);
+ }
+
+ @Test
+ public void testConstructor_MembersNotNull() {
+ assertThat(mHolder.titleView).isNotNull();
+ assertThat(mHolder.summaryView).isNotNull();
+ assertThat(mHolder.iconView).isNotNull();
+ }
+
+ @Test
+ public void testBindViewElements_AllUpdated() {
+ SearchResult result = getSearchResult();
+ mHolder.onBind(result);
+
+ assertThat(mHolder.titleView.getText()).isEqualTo(TITLE);
+ assertThat(mHolder.summaryView.getText()).isEqualTo(SUMMARY);
+ assertThat(mHolder.iconView.getDrawable()).isEqualTo(mIcon);
+ }
+
+ private SearchResult getSearchResult() {
+ Builder builder = new Builder();
+ builder.addTitle(TITLE)
+ .addSummary(SUMMARY)
+ .addRank(1)
+ .addPayload(new IntentPayload(null))
+ .addBreadcrumbs(new ArrayList<String>())
+ .addIcon(mIcon);
+
+ return builder.build();
+ }
+}
--- /dev/null
+/*
+ * 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.search;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.search2.*;
+import com.android.settings.search2.SearchResult.Builder;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.annotation.Config;
+import org.robolectric.Robolectric;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SearchAdapterTest {
+
+ private SearchResultsAdapter mAdapter;
+ private Context mContext;
+ private String mLoaderClassName;
+
+ @Before
+ public void setUp() {
+ mContext = Robolectric.buildActivity(Activity.class).get();
+ mAdapter = new SearchResultsAdapter();
+ mLoaderClassName = DatabaseResultLoader.class.getName();
+ }
+
+ private ArrayList<SearchResult> getIntentSampleResults() {
+ ArrayList<SearchResult> sampleResults = new ArrayList<>();
+ ArrayList<String> breadcrumbs = new ArrayList<>();
+ final Drawable icon = mContext.getDrawable(R.drawable.ic_search_history);
+ final ResultPayload payload = new IntentPayload(null);
+
+ SearchResult.Builder builder = new Builder();
+ builder.addTitle("title")
+ .addSummary("summary")
+ .addRank(1)
+ .addBreadcrumbs(breadcrumbs)
+ .addIcon(icon)
+ .addPayload(payload);
+ sampleResults.add(builder.build());
+
+ builder.addRank(2);
+ sampleResults.add(builder.build());
+
+ builder.addRank(3);
+ sampleResults.add(builder.build());
+ return sampleResults;
+ }
+
+
+ @Test
+ public void testNoResultsAdded_EmptyListReturned() {
+ ArrayList<SearchResult> updatedResults = mAdapter.getSearchResults();
+ assertThat(updatedResults).isEmpty();
+ }
+
+
+ @Test
+ public void testSingleSourceMerge_ExactCopyReturned() {
+ ArrayList<SearchResult> intentResults = getIntentSampleResults();
+ mAdapter.mergeResults(intentResults, mLoaderClassName);
+
+ ArrayList<SearchResult> updatedResults = mAdapter.getSearchResults();
+ assertThat(updatedResults).containsAllIn(intentResults);
+ }
+
+ @Test
+ public void testDuplicateSourceMerge_ExactCopyReturned() {
+ ArrayList<SearchResult> intentResults = getIntentSampleResults();
+ mAdapter.mergeResults(intentResults, mLoaderClassName);
+ mAdapter.mergeResults(intentResults, mLoaderClassName);
+
+ ArrayList<SearchResult> updatedResults = mAdapter.getSearchResults();
+ assertThat(updatedResults).containsAllIn(intentResults);
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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.search;
+
+import android.app.Activity;
+import android.view.Menu;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+import com.android.settings.overlay.FeatureFactory;
+import com.android.settings.search2.SearchFeatureProviderImpl;
+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 static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.verify;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SearchFeatureProviderImplTest {
+ private SearchFeatureProviderImpl mProvider;
+ private Activity mActivity;
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private Menu menu;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mActivity = Robolectric.buildActivity(Activity.class).create().visible().get();
+ mProvider = (SearchFeatureProviderImpl) FeatureFactory.getFactory(mActivity)
+ .getSearchFeatureProvider(mActivity);
+ }
+
+ @Test
+ public void testPassNull_NoError() {
+ mProvider.setUpSearchMenu(null,null);
+ }
+
+ @Test
+ public void testSetUpMenu_HasItemAdded() {
+ mProvider.setUpSearchMenu(menu, mActivity);
+
+ verify(menu).add(anyInt(),anyInt(), anyInt(), anyString());
+ }
+}
--- /dev/null
+/*
+ * 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.search;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import com.android.settings.SettingsRobolectricTestRunner;
+import com.android.settings.TestConfig;
+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 org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+
+import static com.google.common.truth.Truth.assertThat;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SearchResultBuilderTest {
+
+ private Builder mBuilder;
+ private String mTitle;
+ private String mSummary;
+ private ArrayList<String> mBreadcrumbs;
+ private int mRank;
+ private ResultPayload mResultPayload;
+ private Drawable mIcon;
+
+ @Before
+ public void setUp() {
+ mBuilder = new Builder();
+ mTitle = "title";
+ mSummary = "summary";
+ mBreadcrumbs = new ArrayList<>();
+ mRank = 3;
+ mResultPayload = new IntentPayload(null);
+
+ final Context context = ShadowApplication.getInstance().getApplicationContext();
+ mIcon = context.getDrawable(R.drawable.ic_search_history);
+ }
+
+ @Test
+ public void testAllInfo_BuildSearchResult() {
+ mBuilder.addTitle(mTitle)
+ .addSummary(mSummary)
+ .addRank(mRank)
+ .addBreadcrumbs(mBreadcrumbs)
+ .addIcon(mIcon)
+ .addPayload(mResultPayload);
+ SearchResult result = mBuilder.build();
+
+ assertThat(result).isNotNull();
+ assertThat(result.title).isEqualTo(mTitle);
+ assertThat(result.summary).isEqualTo(mSummary);
+ assertThat(result.rank).isEqualTo(mRank);
+ assertThat(result.breadcrumbs).isEqualTo(mBreadcrumbs);
+ assertThat(result.icon).isEqualTo(mIcon);
+ assertThat(result.payload).isEqualTo(mResultPayload);
+ }
+
+ @Test
+ public void testNoTitle_BuildSearchResultException() {
+ mBuilder.addSummary(mSummary)
+ .addRank(mRank)
+ .addBreadcrumbs(mBreadcrumbs)
+ .addIcon(mIcon)
+ .addPayload(mResultPayload);
+
+ SearchResult result = null;
+ try {
+ result = mBuilder.build();
+ } catch (IllegalArgumentException e) {
+ // passes.
+ }
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void testNoSummary_BuildSearchResultException() {
+ mBuilder.addTitle(mTitle)
+ .addRank(mRank)
+ .addBreadcrumbs(mBreadcrumbs)
+ .addIcon(mIcon)
+ .addPayload(mResultPayload);
+
+ SearchResult result = null;
+ try {
+ result = mBuilder.build();
+ } catch (IllegalArgumentException e) {
+ // passes.
+ }
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void testNoRank_BuildSearchResultException() {
+ mBuilder.addTitle(mTitle)
+ .addSummary(mSummary)
+ .addBreadcrumbs(mBreadcrumbs)
+ .addIcon(mIcon)
+ .addPayload(mResultPayload);
+
+ SearchResult result = null;
+ try {
+ result = mBuilder.build();
+ } catch (IllegalArgumentException e) {
+ // passes.
+ }
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void testNoBreadcrumbs_BuildSearchResultException() {
+ mBuilder.addTitle(mTitle)
+ .addSummary(mSummary)
+ .addRank(mRank)
+ .addIcon(mIcon)
+ .addPayload(mResultPayload);
+
+ SearchResult result = null;
+ try {
+ result = mBuilder.build();
+ } catch (IllegalArgumentException e) {
+ // passes.
+ }
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void testNoIcon_BuildSearchResultException() {
+ mBuilder.addTitle(mTitle)
+ .addSummary(mSummary)
+ .addRank(mRank)
+ .addBreadcrumbs(mBreadcrumbs)
+ .addPayload(mResultPayload);
+
+ SearchResult result = null;
+ try {
+ result = mBuilder.build();
+ } catch (IllegalArgumentException e) {
+ // passes.
+ }
+ assertThat(result).isNull();
+ }
+
+ @Test
+ public void testNoPayload_BuildSearchResultException() {
+ mBuilder.addTitle(mTitle)
+ .addSummary(mSummary)
+ .addRank(mRank)
+ .addBreadcrumbs(mBreadcrumbs)
+ .addIcon(mIcon);
+
+ SearchResult result = null;
+ try {
+ result = mBuilder.build();
+ } catch (IllegalArgumentException e) {
+ // passes.
+ }
+ assertThat(result).isNull();
+ }
+}
+
+
import com.android.settings.localepicker.LocaleFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.overlay.SupportFeatureProvider;
+import com.android.settings.search2.SearchFeatureProvider;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
public final LocaleFeatureProvider localeFeatureProvider;
public final ApplicationFeatureProvider applicationFeatureProvider;
public final EnterprisePrivacyFeatureProvider enterprisePrivacyFeatureProvider;
+ public final SearchFeatureProvider searchFeatureProvider;
/**
* Call this in {@code @Before} method of the test class to use fake factory.
localeFeatureProvider = mock(LocaleFeatureProvider.class);
applicationFeatureProvider = mock(ApplicationFeatureProvider.class);
enterprisePrivacyFeatureProvider = mock(EnterprisePrivacyFeatureProvider.class);
+ searchFeatureProvider = mock(SearchFeatureProvider.class);
}
@Override
public EnterprisePrivacyFeatureProvider getEnterprisePrivacyFeatureProvider(Context context) {
return enterprisePrivacyFeatureProvider;
}
+
+ @Override
+ public SearchFeatureProvider getSearchFeatureProvider(Context context) {
+ return searchFeatureProvider;
+ }
}