SearchFragment and SearchBar in support-leanback

Change-Id: Id958ad47462d9efd2b5b770c0391477190e0d66a
diff --git a/v17/leanback/res/layout/lb_search_bar.xml b/v17/leanback/res/layout/lb_search_bar.xml
new file mode 100644
index 0000000..55eeda3
--- /dev/null
+++ b/v17/leanback/res/layout/lb_search_bar.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<android.support.v17.leanback.widget.SearchBar
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/lb_search_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" >
+
+    <RelativeLayout
+            android:id="@+id/lb_search_bar_layout"
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/lb_search_bar_height"
+            android:clipChildren="false"
+            android:layout_alignParentTop="true"
+            android:layout_gravity="top"
+            android:background="@color/lb_search_bar_background" >
+        <FrameLayout
+                android:id="@+id/lb_search_bar_items"
+                android:layout_width="@dimen/lb_search_bar_items_width"
+                android:layout_height="wrap_content"
+                android:layout_gravity="top"
+                android:layout_marginLeft="@dimen/lb_search_browse_row_padding_left"
+                android:layout_marginTop="@dimen/lb_search_bar_items_layout_margin_top"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentTop="true"
+                android:orientation="horizontal"
+                android:background="@android:color/transparent"
+                android:layout_weight="1">
+                <EditText
+                        android:id="@+id/lb_search_text_editor"
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"
+                        android:cursorVisible="true"
+                        android:editable="true"
+                        android:background="@null"
+                        android:fontFamily="sans-serif-light"
+                        android:imeOptions="normal|flagNoExtractUi"
+                        android:inputType="text|textAutoComplete"
+                        android:singleLine="true"
+                        android:textColor="@color/lb_search_text_primary_color"
+                        android:textCursorDrawable="@null"
+                        android:textSize="34sp"/>
+        </FrameLayout>
+    </RelativeLayout>
+</android.support.v17.leanback.widget.SearchBar>
diff --git a/v17/leanback/res/layout/lb_search_fragment.xml b/v17/leanback/res/layout/lb_search_fragment.xml
new file mode 100644
index 0000000..0676628
--- /dev/null
+++ b/v17/leanback/res/layout/lb_search_fragment.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:id="@+id/lb_search_dummy"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent">
+
+    <!-- The outer frame (search_dummy) exists to workaround the
+            problem where clients who embed us using a fragment tag in their
+            xml layout will override the view id.  This causes problems managing
+            child fragments.  Everything within the BrowseFrameLayout below
+            should not be affected by usage. -->
+    <RelativeLayout
+            android:id="@+id/lb_search_frame"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" >
+
+        <FrameLayout
+                android:id="@+id/lb_results_frame"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_alignParentBottom="true"
+                android:layout_below="@id/lb_search_bar_layout"
+                android:layout_marginTop="@dimen/lb_search_bar_height"
+                android:clipChildren="false"
+                android:clipToPadding="false"
+                android:visibility="gone" >
+
+            <!-- Will be replaced by RowContainerFragment -->
+            <FrameLayout
+                    android:id="@+id/lb_results_container"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/lb_search_fragment_results_container_height"
+                    android:background="@android:color/transparent"
+                    android:clipChildren="false"
+                    android:clipToPadding="false"
+                    android:focusable="false"
+                    android:visibility="visible" />
+
+        </FrameLayout>
+
+        <include
+                android:id="@+id/lb_search_bar"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_alignParentTop="true"
+                layout="@layout/lb_search_bar" />
+    </RelativeLayout>
+</FrameLayout>
diff --git a/v17/leanback/res/values/colors.xml b/v17/leanback/res/values/colors.xml
index 2b1b9e3..a8ae64b 100644
--- a/v17/leanback/res/values/colors.xml
+++ b/v17/leanback/res/values/colors.xml
@@ -48,4 +48,8 @@
     <color name="lb_view_dim_mask_color">#000000</color>
     <item name="lb_view_dimmed_level" type="dimen">60%</item>
 
+    <color name="lb_search_background">#D0000000</color>
+    <color name="lb_search_bar_background">#992D2D2D</color>
+    <color name="lb_search_result_background">#FF2D2D2D</color>
+    <color name="lb_search_text_primary_color">#FFFCFCFC</color>
 </resources>
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
index ec62496..cbeb129 100644
--- a/v17/leanback/res/values/dimens.xml
+++ b/v17/leanback/res/values/dimens.xml
@@ -14,9 +14,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
+<resources>
     <dimen name="lb_card_width">140dip</dimen>
     <dimen name="lb_card_height">200dip</dimen>
     <dimen name="lb_card_title_text_size">14sp</dimen>
@@ -92,4 +90,12 @@
     <!-- One-sided increase in browse item size if height > width -->
     <dimen name="lb_browse_item_zoom_width">12dp</dimen>
 
+    <!-- Search bar -->
+    <dimen name="lb_search_bar_height">120dip</dimen>
+    <dimen name="lb_search_bar_items_layout_margin_top">42dip</dimen>
+    <dimen name="lb_search_bar_items_width">660dip</dimen>
+
+    <!-- Search Fragment -->
+    <dimen name="lb_search_browse_row_padding_left">48dip</dimen>
+    <dimen name="lb_search_fragment_results_container_height">425dip</dimen>
 </resources>
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
new file mode 100644
index 0000000..9932a9f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.app.Fragment;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemSelectedListener;
+import android.support.v17.leanback.widget.SearchBar;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.RelativeLayout;
+import android.support.v17.leanback.R;
+
+/**
+ * A fragment to handle searches
+ */
+public class SearchFragment extends Fragment {
+    private static final String TAG = SearchFragment.class.getSimpleName();
+    private static final String ARG_QUERY = SearchFragment.class.getCanonicalName() + ".query";
+
+    /**
+     * Search API exposed to application
+     */
+    public static interface SearchResultProvider {
+        /**
+         * When using the SearchFragment, this is the entry point for the application
+         * to receive the search query and provide the corresponding results.
+         *
+         * The returned ObjectAdapter can be populated asynchronously.
+         *
+         * As results are retrieved, the application should use the data set notification methods
+         * on the ObjectAdapter to instruct the SearchFragment to update the results.
+         *
+         * @param searchQuery The search query entered by the user.
+         * @return An ObjectAdapter containing the structured results for the provided search query.
+         */
+        public ObjectAdapter results(String searchQuery);
+    }
+
+    private final RowsFragment mRowsFragment = new RowsFragment();
+    private final Handler mHandler = new Handler();
+
+    private RelativeLayout mSearchFrame;
+    private SearchBar mSearchBar;
+    private FrameLayout mResultsFrame;
+    private SearchResultProvider mProvider;
+    private String mPendingQuery = null;
+
+    /**
+     * @param args Bundle to use for the arguments, if null a new Bundle will be created.
+     */
+    public static Bundle createArgs(Bundle args, String query) {
+        if (args == null) {
+            args = new Bundle();
+        }
+        args.putString(ARG_QUERY, query);
+        return args;
+    }
+
+    /**
+     * Create a search fragment with a given search query to start with
+     *
+     * You should only use this if you need to start the search fragment with a pre-filled query
+     *
+     * @param query the search query to start with
+     * @return a new SearchFragment
+     */
+    public static SearchFragment newInstance(String query) {
+        SearchFragment fragment = new SearchFragment();
+        Bundle args = createArgs(null, query);
+        fragment.setArguments(args);
+        return fragment;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View root = inflater.inflate(R.layout.lb_search_fragment, container, false);
+
+        mSearchFrame = (RelativeLayout) root.findViewById(R.id.lb_search_frame);
+        mResultsFrame = (FrameLayout) root.findViewById(R.id.lb_results_frame);
+        mSearchBar = (SearchBar) mSearchFrame.findViewById(R.id.lb_search_bar);
+        mSearchBar.setSearchBarListener(new SearchBar.SearchBarListener() {
+            @Override
+            public void onSearchQueryChanged(String searchQuery) {
+                if (null != mProvider) {
+                    retrieveResults(searchQuery);
+                } else {
+                    mPendingQuery = searchQuery;
+                }
+            }
+        });
+        Bundle args = getArguments();
+        if (null != args) {
+            String query = args.getString(ARG_QUERY, "");
+            mSearchBar.setSearchQuery(query);
+        }
+
+        // Inject the RowsFragment in the results container
+        getChildFragmentManager().beginTransaction()
+                .replace(R.id.lb_results_container, mRowsFragment).commit();
+        return root;
+    }
+
+    /**
+     * Set the search provider, which is responsible for returning items given
+     * a search term
+     *
+     * @param searchResultProvider the search provider
+     */
+    public void setSearchResultProvider(SearchResultProvider searchResultProvider) {
+        mProvider = searchResultProvider;
+        onSetSearchResultProvider();
+    }
+
+    /**
+     * Set background parameters
+     * @param params the background parameters
+     */
+    public void setBackgroundParams(BackgroundParams params) {
+        mRowsFragment.setBackgroundParams(params);
+    }
+
+    /**
+     * Sets an item selection listener.
+     * @param listener the item selection listener
+     */
+    public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+        mRowsFragment.setOnItemSelectedListener(listener);
+    }
+
+    private void retrieveResults(String searchQuery) {
+        ObjectAdapter adapter = mProvider.results(searchQuery);
+        mRowsFragment.setAdapter(adapter);
+        mResultsFrame.setVisibility(View.VISIBLE);
+    }
+
+    private void onSetSearchResultProvider() {
+        executePendingQuery();
+    }
+
+    private void executePendingQuery() {
+        if (null != mPendingQuery) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    retrieveResults(mPendingQuery);
+                    mPendingQuery = null;
+                }
+            });
+        }
+    }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
new file mode 100644
index 0000000..cb3f253
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.RelativeLayout;
+import android.support.v17.leanback.R;
+
+/**
+ * SearchBar is a search widget.
+ */
+public class SearchBar extends RelativeLayout {
+    private static final String TAG = SearchBar.class.getSimpleName();
+
+    /**
+     * Listener for search query changes
+     */
+    public interface SearchBarListener {
+
+        /**
+         * Method invoked when the search bar detects a change in the query
+         * @param searchQuery the current full query
+         */
+        public void onSearchQueryChanged(String searchQuery);
+    }
+
+    private SearchBarListener mSearchBarListener;
+    private EditText mSearchTextEditor;
+    private String mSearchQuery;
+    private final Handler mHandler = new Handler();
+
+    public SearchBar(Context context) {
+        this(context, null);
+    }
+
+    public SearchBar(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public SearchBar(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mSearchQuery = "";
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mSearchTextEditor = (EditText)findViewById(R.id.lb_search_text_editor);
+        mSearchTextEditor.setOnFocusChangeListener(new OnFocusChangeListener() {
+            @Override
+            public void onFocusChange(View view, boolean hasFocus) {
+                if (hasFocus) {
+                    showNativeKeyboard();
+                }
+            }
+        });
+        mSearchTextEditor.addTextChangedListener(new TextWatcher() {
+            @Override
+            public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+
+            }
+
+            @Override
+            public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
+                setSearchQuery(charSequence.toString());
+            }
+
+            @Override
+            public void afterTextChanged(Editable editable) {
+
+            }
+        });
+        mSearchTextEditor.setFocusable(true);
+        mSearchTextEditor.setVisibility(VISIBLE);
+        mSearchTextEditor.requestFocus();
+    }
+
+    /**
+     * Set a listener for when the term search changes
+     * @param listener
+     */
+    public void setSearchBarListener(SearchBarListener listener) {
+        mSearchBarListener = listener;
+    }
+
+    /**
+     * Set the search query
+     * @param query the search query to use
+     */
+    public void setSearchQuery(String query) {
+        if (query.equals(mSearchQuery)) {
+            return;
+        }
+        mSearchQuery = query;
+        if (null != mSearchBarListener) {
+            mSearchBarListener.onSearchQueryChanged(mSearchQuery);
+        }
+    }
+
+    protected void showNativeKeyboard() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mSearchTextEditor.requestFocusFromTouch();
+                mSearchTextEditor.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
+                        SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN,
+                        mSearchTextEditor.getWidth(), mSearchTextEditor.getHeight(), 0));
+                mSearchTextEditor.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
+                        SystemClock.uptimeMillis(), MotionEvent.ACTION_UP,
+                        mSearchTextEditor.getWidth(), mSearchTextEditor.getHeight(), 0));
+            }
+        });
+    }
+
+}