Added search bar animations to NUI toolbar.

Bug: 181512198
Test: MainActivityIntegrationTest
PiperOrigin-RevId: 181771027
Change-Id: Id2b6d35cec928cbd2fed8ad75a9da007af9e826e
diff --git a/java/com/android/dialer/main/impl/MainActivity.java b/java/com/android/dialer/main/impl/MainActivity.java
index 0884c11..fc7e670 100644
--- a/java/com/android/dialer/main/impl/MainActivity.java
+++ b/java/com/android/dialer/main/impl/MainActivity.java
@@ -31,6 +31,8 @@
 import com.android.dialer.contactsfragment.ContactsFragment.Header;
 import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener;
 import com.android.dialer.main.impl.BottomNavBar.OnBottomNavTabSelectedListener;
+import com.android.dialer.main.impl.toolbar.MainToolbar;
+import com.android.dialer.main.impl.toolbar.SearchBarListener;
 import com.android.dialer.speeddial.SpeedDialFragment;
 import com.android.dialer.voicemail.listui.NewVoicemailFragment;
 
@@ -58,7 +60,9 @@
 
   private void initLayout() {
     findViewById(R.id.fab).setOnClickListener(this);
-    setSupportActionBar(findViewById(R.id.toolbar));
+    MainToolbar toolbar = findViewById(R.id.toolbar);
+    toolbar.setSearchBarListener(new MainSearchBarListener());
+    setSupportActionBar(toolbar);
     BottomNavBar navBar = findViewById(R.id.bottom_nav_bar);
     navBar.setOnTabSelectedListener(new MainBottomNavBarBottomNavTabListener());
     navBar.selectTab(BottomNavBar.TabIndex.SPEED_DIAL);
@@ -79,6 +83,28 @@
   }
 
   /**
+   * Implementation of {@link SearchBarListener} that holds the logic for how to handle search bar
+   * events.
+   */
+  private static final class MainSearchBarListener implements SearchBarListener {
+
+    @Override
+    public void onSearchQueryUpdated(String query) {}
+
+    @Override
+    public void onSearchBackButtonClicked() {}
+
+    @Override
+    public void onVoiceButtonClicked(VoiceSearchResultCallback voiceSearchResultCallback) {}
+
+    @Override
+    public void openSettings() {}
+
+    @Override
+    public void sendFeedback() {}
+  }
+
+  /**
    * Implementation of {@link OnBottomNavTabSelectedListener} that handles logic for showing each of
    * the main tabs.
    */
diff --git a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java
index 19c763c..6d9b7da 100644
--- a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java
+++ b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java
@@ -22,11 +22,12 @@
 import android.util.AttributeSet;
 import android.view.MenuItem;
 import android.widget.ImageButton;
-import android.widget.Toast;
 
 /** Toolbar for {@link com.android.dialer.main.impl.MainActivity}. */
 public final class MainToolbar extends Toolbar implements OnMenuItemClickListener {
 
+  private SearchBarListener listener;
+
   public MainToolbar(Context context, AttributeSet attrs) {
     super(context, attrs);
   }
@@ -40,23 +41,20 @@
     overflowMenu.setOnMenuItemClickListener(this);
     optionsMenuButton.setOnClickListener(v -> overflowMenu.show());
     optionsMenuButton.setOnTouchListener(overflowMenu.getDragToOpenListener());
-
-    findViewById(R.id.voice_search_button).setOnClickListener(v -> onVoiceIconClicked());
-    findViewById(R.id.search_box_collapsed).setOnClickListener(v -> onSearchBarClicked());
   }
 
   @Override
   public boolean onMenuItemClick(MenuItem menuItem) {
-    Toast.makeText(getContext(), "Not yet implemented", Toast.LENGTH_SHORT).show();
-    // TODO(calderwoodra): implement menu item clicks
+    if (menuItem.getItemId() == R.id.settings) {
+      listener.openSettings();
+    } else if (menuItem.getItemId() == R.id.feedback) {
+      listener.sendFeedback();
+    }
     return false;
   }
 
-  private void onVoiceIconClicked() {
-    // TODO(calderwoodra): take voice input
-  }
-
-  private void onSearchBarClicked() {
-    // TODO(calderwoodra): open search UI
+  public void setSearchBarListener(SearchBarListener listener) {
+    this.listener = listener;
+    ((SearchBarView) findViewById(R.id.search_view_container)).setSearchBarListener(listener);
   }
 }
diff --git a/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java b/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java
new file mode 100644
index 0000000..32258d9
--- /dev/null
+++ b/java/com/android/dialer/main/impl/toolbar/SearchBarListener.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2018 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.dialer.main.impl.toolbar;
+
+/** Useful callback for {@link SearchBarView} listeners. */
+public interface SearchBarListener {
+
+  /** Called when the search query updates. */
+  void onSearchQueryUpdated(String query);
+
+  /** Called when the back button is clicked in the search bar. */
+  void onSearchBackButtonClicked();
+
+  /** Called when the voice search button is clicked. */
+  void onVoiceButtonClicked(VoiceSearchResultCallback voiceSearchResultCallback);
+
+  /** Called when the settings option is selected from the search menu. */
+  void openSettings();
+
+  /** Called when send feedback is selected from the search menu. */
+  void sendFeedback();
+
+  /** Interface for returning voice results to the search bar. */
+  interface VoiceSearchResultCallback {
+
+    /** Sets the voice results in the search bar and expands the search UI. */
+    void setResult(String result);
+  }
+}
diff --git a/java/com/android/dialer/main/impl/toolbar/SearchBarView.java b/java/com/android/dialer/main/impl/toolbar/SearchBarView.java
index b3b27ef..35c3cee 100644
--- a/java/com/android/dialer/main/impl/toolbar/SearchBarView.java
+++ b/java/com/android/dialer/main/impl/toolbar/SearchBarView.java
@@ -16,16 +16,180 @@
 
 package com.android.dialer.main.impl.toolbar;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.Context;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
 import android.util.AttributeSet;
+import android.view.View;
+import android.widget.EditText;
 import android.widget.FrameLayout;
+import com.android.dialer.animation.AnimUtils;
+import com.android.dialer.util.DialerUtils;
+import com.google.common.base.Optional;
 
-/** */
+/** Search bar for {@link MainToolbar}. Mostly used to handle expand and collapse animation. */
 final class SearchBarView extends FrameLayout {
 
+  private static final int ANIMATION_DURATION = 200;
+  private static final float EXPAND_MARGIN_FRACTION_START = 0.8f;
+
+  private final float margin;
+  private final float animationEndHeight;
+
+  private SearchBarListener listener;
+  private EditText searchBox;
+
+  private int initialHeight;
+  private boolean isExpanded;
+  private View searchBoxCollapsed;
+  private View searchBoxExpanded;
+  private View clearButton;
+
   public SearchBarView(@NonNull Context context, @Nullable AttributeSet attrs) {
     super(context, attrs);
+    margin = getContext().getResources().getDimension(R.dimen.search_bar_margin);
+    animationEndHeight =
+        getContext().getResources().getDimension(R.dimen.expanded_search_bar_height);
+  }
+
+  @Override
+  protected void onFinishInflate() {
+    super.onFinishInflate();
+    clearButton = findViewById(R.id.search_clear_button);
+    searchBox = findViewById(R.id.search_view);
+    searchBoxCollapsed = findViewById(R.id.search_box_collapsed);
+    searchBoxExpanded = findViewById(R.id.search_box_expanded);
+
+    setOnClickListener(v -> expand(true, Optional.absent()));
+    findViewById(R.id.voice_search_button).setOnClickListener(v -> voiceSearchClicked());
+    findViewById(R.id.search_back_button).setOnClickListener(v -> onSearchBackButtonClicked());
+    clearButton.setOnClickListener(v -> onSearchClearButtonClicked());
+    searchBox.addTextChangedListener(new SearchBoxTextWatcher());
+  }
+
+  private void onSearchClearButtonClicked() {
+    searchBox.setText("");
+  }
+
+  private void onSearchBackButtonClicked() {
+    listener.onSearchBackButtonClicked();
+    collapse(true);
+  }
+
+  private void voiceSearchClicked() {
+    listener.onVoiceButtonClicked(
+        result -> {
+          if (!TextUtils.isEmpty(result)) {
+            expand(true, Optional.of(result));
+          }
+        });
+  }
+
+  /** Expand the search bar and populate it with text if any exists. */
+  private void expand(boolean animate, Optional<String> text) {
+    if (isExpanded) {
+      return;
+    }
+    initialHeight = getHeight();
+
+    int duration = animate ? ANIMATION_DURATION : 0;
+    searchBoxExpanded.setVisibility(VISIBLE);
+    AnimUtils.crossFadeViews(searchBoxExpanded, searchBoxCollapsed, duration);
+    ValueAnimator animator = ValueAnimator.ofFloat(EXPAND_MARGIN_FRACTION_START, 0f);
+    animator.addUpdateListener(animation -> setMargins((Float) animation.getAnimatedValue()));
+    animator.setDuration(duration);
+    animator.addListener(
+        new AnimatorListenerAdapter() {
+          @Override
+          public void onAnimationStart(Animator animation) {
+            super.onAnimationStart(animation);
+            DialerUtils.showInputMethod(searchBox);
+            isExpanded = true;
+          }
+
+          @Override
+          public void onAnimationEnd(Animator animation) {
+            super.onAnimationEnd(animation);
+            if (text.isPresent()) {
+              searchBox.setText(text.get());
+            }
+            searchBox.requestFocus();
+          }
+        });
+    animator.start();
+  }
+
+  /** Collapse the search bar and clear it's text. */
+  private void collapse(boolean animate) {
+    if (!isExpanded) {
+      return;
+    }
+
+    int duration = animate ? ANIMATION_DURATION : 0;
+    AnimUtils.crossFadeViews(searchBoxCollapsed, searchBoxExpanded, duration);
+    ValueAnimator animator = ValueAnimator.ofFloat(0f, EXPAND_MARGIN_FRACTION_START);
+    animator.addUpdateListener(animation -> setMargins((Float) animation.getAnimatedValue()));
+    animator.setDuration(duration);
+
+    animator.addListener(
+        new AnimatorListenerAdapter() {
+          @Override
+          public void onAnimationStart(Animator animation) {
+            super.onAnimationStart(animation);
+            DialerUtils.hideInputMethod(searchBox);
+            isExpanded = false;
+          }
+
+          @Override
+          public void onAnimationEnd(Animator animation) {
+            super.onAnimationEnd(animation);
+            searchBox.setText("");
+            searchBoxExpanded.setVisibility(INVISIBLE);
+          }
+        });
+    animator.start();
+  }
+
+  /**
+   * Assigns margins to the search box as a fraction of its maximum margin size
+   *
+   * @param fraction How large the margins should be as a fraction of their full size
+   */
+  private void setMargins(float fraction) {
+    int margin = (int) (this.margin * fraction);
+    MarginLayoutParams params = (MarginLayoutParams) getLayoutParams();
+    params.topMargin = margin;
+    params.bottomMargin = margin;
+    params.leftMargin = margin;
+    params.rightMargin = margin;
+    searchBoxExpanded.getLayoutParams().height =
+        (int) (animationEndHeight - (animationEndHeight - initialHeight) * fraction);
+    requestLayout();
+  }
+
+  public void setSearchBarListener(SearchBarListener listener) {
+    this.listener = listener;
+  }
+
+  /** Handles logic for text changes in the search box. */
+  private class SearchBoxTextWatcher implements TextWatcher {
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+
+    @Override
+    public void onTextChanged(CharSequence s, int start, int before, int count) {}
+
+    @Override
+    public void afterTextChanged(Editable s) {
+      clearButton.setVisibility(TextUtils.isEmpty(s) ? GONE : VISIBLE);
+      listener.onSearchQueryUpdated(s.toString());
+    }
   }
 }
diff --git a/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml b/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml
index f814a76..4dbd4bf 100644
--- a/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml
+++ b/java/com/android/dialer/main/impl/toolbar/res/layout/expanded_search_bar.xml
@@ -17,14 +17,14 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/search_box_expanded"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:visibility="gone">
+    android:layout_height="wrap_content"
+    android:visibility="invisible">
 
   <ImageButton
       android:id="@+id/search_back_button"
       android:layout_width="48dp"
       android:layout_height="48dp"
-      android:layout_marginStart="16dp"
+      android:layout_marginStart="8dp"
       android:layout_centerVertical="true"
       android:background="?attr/selectableItemBackgroundBorderless"
       android:contentDescription="@string/action_menu_back_from_search"
@@ -39,17 +39,19 @@
       android:layout_toStartOf="@+id/search_close_button"
       android:layout_centerVertical="true"
       android:layout_marginStart="8dp"
+      android:minHeight="48dp"
       android:background="@null"
       android:imeOptions="flagNoExtractUi"
       android:inputType="textFilter"
       android:maxLines="1"
+      android:hint="@string/dialer_hint_find_contact"
       android:textColor="@color/dialer_secondary_text_color"
       android:textColorHint="@color/dialer_edit_text_hint_color"
       android:textCursorDrawable="@drawable/custom_cursor"
       android:textSize="16sp"/>
 
   <ImageView
-      android:id="@+id/search_close_button"
+      android:id="@+id/search_clear_button"
       android:layout_width="48dp"
       android:layout_height="48dp"
       android:layout_alignParentEnd="true"
@@ -58,5 +60,6 @@
       android:background="?attr/selectableItemBackgroundBorderless"
       android:contentDescription="@string/description_clear_search"
       android:src="@drawable/quantum_ic_close_vd_theme_24"
-      android:tint="@color/dialer_secondary_text_color"/>
+      android:tint="@color/dialer_secondary_text_color"
+      android:visibility="gone"/>
 </RelativeLayout>
\ No newline at end of file
diff --git a/java/com/android/dialer/main/impl/toolbar/res/layout/toolbar_layout.xml b/java/com/android/dialer/main/impl/toolbar/res/layout/toolbar_layout.xml
index 27b37e8..9b0086b 100644
--- a/java/com/android/dialer/main/impl/toolbar/res/layout/toolbar_layout.xml
+++ b/java/com/android/dialer/main/impl/toolbar/res/layout/toolbar_layout.xml
@@ -22,25 +22,21 @@
     android:minHeight="?attr/actionBarSize"
     android:background="@color/dialer_theme_color"
     app:contentInsetStart="0dp"
-    app:contentInsetEnd="0dp"
-    app:theme="@style/ThemeOverlay.AppCompat.Light">
+    app:contentInsetEnd="0dp">
 
   <com.android.dialer.main.impl.toolbar.SearchBarView
       android:id="@+id/search_view_container"
       android:layout_width="match_parent"
-      android:layout_height="48dp"
-      android:layout_marginStart="8dp"
-      android:layout_marginTop="8dp"
-      android:layout_marginBottom="8dp"
-      android:layout_marginEnd="8dp"
+      android:layout_height="wrap_content"
+      android:layout_margin="@dimen/search_bar_margin"
       android:background="@drawable/rounded_corner"
-      android:elevation="4dp"
-      android:theme="@style/Theme.AppCompat.Light">
+      android:elevation="4dp">
 
     <RelativeLayout
       android:id="@+id/search_box_collapsed"
       android:layout_width="match_parent"
-      android:layout_height="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_gravity="center_vertical"
       android:background="?android:selectableItemBackground"
       android:gravity="center_vertical">
 
@@ -58,12 +54,12 @@
       <TextView
           android:id="@+id/search_box_start_search"
           android:layout_width="wrap_content"
-          android:layout_height="match_parent"
+          android:layout_height="wrap_content"
           android:layout_toEndOf="@+id/search_magnifying_glass"
           android:layout_toStartOf="@+id/voice_search_button"
           android:layout_marginStart="8dp"
+          android:layout_centerVertical="true"
           android:fontFamily="sans-serif"
-          android:gravity="center_vertical"
           android:hint="@string/dialer_hint_find_contact"
           android:textColorHint="@color/dialer_secondary_text_color"
           android:textSize="16dp"/>
diff --git a/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml b/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml
new file mode 100644
index 0000000..f54f053
--- /dev/null
+++ b/java/com/android/dialer/main/impl/toolbar/res/values/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<resources>
+  <dimen name="search_bar_margin">8dp</dimen>
+  <dimen name="expanded_search_bar_height">60dp</dimen>
+</resources>
\ No newline at end of file