Added Dialpad to NUI.

This change adds the existing dialpad to the new UI and the animation logic
required to show/hide it along with the toolbar. A follow up CL will come
afterwards to combine these animations with the toolbar expanding/collapsing.

known issue: bottom nav appears over the dialpad.

Bug: 181512198
Test: MainActivityTest
PiperOrigin-RevId: 181777370
Change-Id: Ief1591174ebca3a709df6d1d38c8b8ecbdc1878e
diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java
index 0418009..07e401c 100644
--- a/java/com/android/dialer/dialpadview/DialpadFragment.java
+++ b/java/com/android/dialer/dialpadview/DialpadFragment.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
@@ -60,6 +61,9 @@
 import android.view.MenuItem;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.AnimationUtils;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
 import android.widget.EditText;
@@ -74,6 +78,7 @@
 import com.android.dialer.callintent.CallInitiationType;
 import com.android.dialer.callintent.CallIntentBuilder;
 import com.android.dialer.calllogutils.PhoneAccountUtils;
+import com.android.dialer.common.Assert;
 import com.android.dialer.common.FragmentUtils;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.DialerExecutor;
@@ -89,6 +94,7 @@
 import com.android.dialer.telecom.TelecomUtil;
 import com.android.dialer.util.CallUtil;
 import com.android.dialer.util.PermissionsUtil;
+import com.android.dialer.util.ViewUtil;
 import com.android.dialer.widget.FloatingActionButtonController;
 import com.google.common.base.Ascii;
 import com.google.common.base.Optional;
@@ -177,7 +183,11 @@
   private boolean firstLaunch = false;
   private boolean animate = false;
 
+  private boolean isLayoutRtl;
+  private boolean isLandscape;
+
   private DialerExecutor<String> initPhoneNumberFormattingTextWatcherExecutor;
+  private boolean isDialpadSlideUp;
 
   /**
    * Determines whether an add call operation is requested.
@@ -426,6 +436,14 @@
     return fragmentView;
   }
 
+  @Override
+  public void onAttach(Context context) {
+    super.onAttach(context);
+    isLayoutRtl = ViewUtil.isRtl();
+    isLandscape =
+        getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
+  }
+
   private String getCurrentCountryIso() {
     if (currentCountryIsoForTesting.isPresent()) {
       return currentCountryIsoForTesting.get();
@@ -1508,6 +1526,44 @@
     }
   }
 
+  /** Animate the dialpad down off the screen. */
+  public void slideDown(boolean animate, AnimationListener listener) {
+    Assert.checkArgument(isDialpadSlideUp);
+    isDialpadSlideUp = false;
+    int animation;
+    if (isLandscape) {
+      animation = isLayoutRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right;
+    } else {
+      animation = R.anim.dialpad_slide_out_bottom;
+    }
+    Animation slideDown = AnimationUtils.loadAnimation(getContext(), animation);
+    slideDown.setInterpolator(AnimUtils.EASE_OUT);
+    slideDown.setAnimationListener(listener);
+    slideDown.setDuration(animate ? dialpadSlideInDuration : 0);
+    getView().startAnimation(slideDown);
+  }
+
+  /** Animate the dialpad up onto the screen. */
+  public void slideUp(boolean animate) {
+    Assert.checkArgument(!isDialpadSlideUp);
+    isDialpadSlideUp = true;
+    int animation;
+    if (isLandscape) {
+      animation = isLayoutRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right;
+    } else {
+      animation = R.anim.dialpad_slide_in_bottom;
+    }
+    Animation slideUp = AnimationUtils.loadAnimation(getContext(), animation);
+    slideUp.setInterpolator(AnimUtils.EASE_IN);
+    slideUp.setDuration(animate ? dialpadSlideInDuration : 0);
+    getView().startAnimation(slideUp);
+  }
+
+  /** Returns the text in the dialpad */
+  public String getQuery() {
+    return digits.getText().toString();
+  }
+
   public interface OnDialpadQueryChangedListener {
 
     void onDialpadQueryChanged(String query);
diff --git a/java/com/android/dialer/main/impl/MainActivity.java b/java/com/android/dialer/main/impl/MainActivity.java
index fc7e670..8bdb295 100644
--- a/java/com/android/dialer/main/impl/MainActivity.java
+++ b/java/com/android/dialer/main/impl/MainActivity.java
@@ -21,24 +21,35 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.ContactsContract.QuickContact;
+import android.support.design.widget.FloatingActionButton;
 import android.support.v4.app.FragmentTransaction;
 import android.support.v7.app.AppCompatActivity;
-import android.view.View;
+import android.text.TextUtils;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
 import android.widget.ImageView;
 import com.android.dialer.calllog.ui.NewCallLogFragment;
+import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.contactsfragment.ContactsFragment;
 import com.android.dialer.contactsfragment.ContactsFragment.Header;
 import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener;
+import com.android.dialer.dialpadview.DialpadFragment;
+import com.android.dialer.dialpadview.DialpadFragment.DialpadListener;
+import com.android.dialer.dialpadview.DialpadFragment.LastOutgoingCallCallback;
+import com.android.dialer.dialpadview.DialpadFragment.OnDialpadQueryChangedListener;
 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.searchfragment.list.NewSearchFragment;
 import com.android.dialer.speeddial.SpeedDialFragment;
 import com.android.dialer.voicemail.listui.NewVoicemailFragment;
 
 /** This is the main activity for dialer. It hosts favorites, call log, search, dialpad, etc... */
 public final class MainActivity extends AppCompatActivity
-    implements View.OnClickListener, OnContactSelectedListener {
+    implements OnContactSelectedListener, OnDialpadQueryChangedListener, DialpadListener {
+
+  private SearchController searchController;
 
   /**
    * @param context Context of the application package implementing MainActivity class.
@@ -59,29 +70,168 @@
   }
 
   private void initLayout() {
-    findViewById(R.id.fab).setOnClickListener(this);
+    FloatingActionButton fab = findViewById(R.id.fab);
+    fab.setOnClickListener(v -> searchController.showDialpad(true));
+
     MainToolbar toolbar = findViewById(R.id.toolbar);
     toolbar.setSearchBarListener(new MainSearchBarListener());
-    setSupportActionBar(toolbar);
+    searchController = new SearchController(fab, toolbar);
+    setSupportActionBar(findViewById(R.id.toolbar));
+
     BottomNavBar navBar = findViewById(R.id.bottom_nav_bar);
     navBar.setOnTabSelectedListener(new MainBottomNavBarBottomNavTabListener());
+    // TODO(calderwoodra): Implement last tab
     navBar.selectTab(BottomNavBar.TabIndex.SPEED_DIAL);
   }
 
   @Override
-  public void onClick(View v) {
-    if (v.getId() == R.id.fab) {
-      // open dialpad search
-    }
-  }
-
-  @Override
   public void onContactSelected(ImageView photo, Uri contactUri, long contactId) {
     // TODO(calderwoodra): Add impression logging
     QuickContact.showQuickContact(
         this, photo, contactUri, QuickContact.MODE_LARGE, null /* excludeMimes */);
   }
 
+  @Override // OnDialpadQueryChangedListener
+  public void onDialpadQueryChanged(String query) {
+    // TODO(calderwoodra): update search fragment
+  }
+
+  @Override // DialpadListener
+  public void getLastOutgoingCall(LastOutgoingCallCallback callback) {
+    // TODO(calderwoodra): migrate CallLogAsync class outside of dialer/app and call it here.
+  }
+
+  @Override // DialpadListener
+  public void onDialpadShown() {
+    searchController.getDialpadFragment().slideUp(true);
+  }
+
+  @Override // DialpadListener
+  public void onCallPlacedFromDialpad() {
+    // TODO(calderwoodra): logging
+  }
+
+  @Override
+  public void onBackPressed() {
+    if (searchController.onBackPressed()) {
+      return;
+    }
+    super.onBackPressed();
+  }
+
+  /** Search controller for handling all the logic related to hiding/showing search and dialpad. */
+  private final class SearchController {
+
+    private static final String DIALPAD_FRAGMENT_TAG = "dialpad_fragment_tag";
+    private static final String SEARCH_FRAGMENT_TAG = "search_fragment_tag";
+
+    private final FloatingActionButton fab;
+    private final MainToolbar toolbar;
+
+    private boolean isDialpadVisible;
+    private boolean isSearchVisible;
+
+    private SearchController(FloatingActionButton fab, MainToolbar toolbar) {
+      this.fab = fab;
+      this.toolbar = toolbar;
+    }
+
+    /** Shows the dialpad, hides the FAB and slides the toolbar off screen. */
+    public void showDialpad(boolean animate) {
+      Assert.checkArgument(!isDialpadVisible);
+      isDialpadVisible = true;
+      isSearchVisible = true;
+
+      fab.hide();
+      toolbar.slideUp(animate);
+      setTitle(R.string.dialpad_activity_title);
+
+      android.app.FragmentTransaction transaction = getFragmentManager().beginTransaction();
+
+      // Show Search
+      if (getSearchFragment() == null) {
+        NewSearchFragment searchFragment = NewSearchFragment.newInstance(false);
+        transaction.add(R.id.search_fragment_container, searchFragment, SEARCH_FRAGMENT_TAG);
+      } else if (!isSearchVisible) {
+        transaction.show(getSearchFragment());
+      }
+
+      // Show Dialpad
+      if (getDialpadFragment() == null) {
+        DialpadFragment dialpadFragment = new DialpadFragment();
+        transaction.add(R.id.search_fragment_container, dialpadFragment, DIALPAD_FRAGMENT_TAG);
+      } else {
+        DialpadFragment dialpadFragment = getDialpadFragment();
+        transaction.show(dialpadFragment);
+      }
+      transaction.commit();
+    }
+
+    /** Hides the dialpad, reveals the FAB and slides the toolbar back onto the screen. */
+    public void hideDialpad(boolean animate) {
+      Assert.checkArgument(isDialpadVisible);
+      isDialpadVisible = false;
+
+      fab.show();
+      toolbar.slideDown(animate);
+      setTitle(R.string.main_activity_label);
+
+      DialpadFragment dialpadFragment = getDialpadFragment();
+      dialpadFragment.setAnimate(animate);
+      dialpadFragment.slideDown(
+          animate,
+          new AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) {}
+
+            @Override
+            public void onAnimationEnd(Animation animation) {
+              if (!(isFinishing() || isDestroyed())) {
+                getFragmentManager().beginTransaction().remove(dialpadFragment).commit();
+              }
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) {}
+          });
+    }
+
+    /**
+     * Should be called when the user presses the back button.
+     *
+     * @return true if {@link #onBackPressed()} handled to action.
+     */
+    public boolean onBackPressed() {
+      if (isDialpadVisible && !TextUtils.isEmpty(getDialpadFragment().getQuery())) {
+        hideDialpad(true);
+        return true;
+      } else if (isSearchVisible) {
+        closeSearch(true);
+        return true;
+      } else {
+        return false;
+      }
+    }
+
+    /** Calls {@link #hideDialpad(boolean)} and removes the search fragment. */
+    private void closeSearch(boolean animate) {
+      Assert.checkArgument(isSearchVisible);
+      if (isDialpadVisible) {
+        hideDialpad(animate);
+      }
+      getFragmentManager().beginTransaction().remove(getSearchFragment()).commit();
+      isSearchVisible = false;
+    }
+
+    private DialpadFragment getDialpadFragment() {
+      return (DialpadFragment) getFragmentManager().findFragmentByTag(DIALPAD_FRAGMENT_TAG);
+    }
+
+    private NewSearchFragment getSearchFragment() {
+      return (NewSearchFragment) getFragmentManager().findFragmentByTag(SEARCH_FRAGMENT_TAG);
+    }
+  }
+
   /**
    * Implementation of {@link SearchBarListener} that holds the logic for how to handle search bar
    * events.
diff --git a/java/com/android/dialer/main/impl/res/layout/bottom_nav_item.xml b/java/com/android/dialer/main/impl/res/layout/bottom_nav_item.xml
index 28ad964..f9f2b61 100644
--- a/java/com/android/dialer/main/impl/res/layout/bottom_nav_item.xml
+++ b/java/com/android/dialer/main/impl/res/layout/bottom_nav_item.xml
@@ -37,5 +37,6 @@
       android:id="@+id/bottom_nav_item_text"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
-      android:textSize="12sp"/>
+      android:textSize="12sp"
+      android:gravity="center_horizontal"/>
 </com.android.dialer.main.impl.BottomNavItem>
\ No newline at end of file
diff --git a/java/com/android/dialer/main/impl/res/layout/main_activity.xml b/java/com/android/dialer/main/impl/res/layout/main_activity.xml
index 31ff19d..0b0652a 100644
--- a/java/com/android/dialer/main/impl/res/layout/main_activity.xml
+++ b/java/com/android/dialer/main/impl/res/layout/main_activity.xml
@@ -21,10 +21,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-  <include
-      android:id="@+id/toolbar"
-      layout="@layout/toolbar_layout"/>
-
+  <!-- Holds SpeedDial, Call Log, Contacts and Voicemail fragments -->
   <FrameLayout
       android:id="@+id/fragment_container"
       android:layout_width="match_parent"
@@ -32,14 +29,26 @@
       android:layout_below="@+id/toolbar"
       android:layout_above="@+id/bottom_nav_bar"/>
 
+  <!-- BottomNavBar -->
   <include
       android:id="@+id/bottom_nav_bar"
       layout="@layout/bottom_nav_bar_layout"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
-      android:layout_gravity="bottom"
       android:layout_alignParentBottom="true"/>
 
+  <!-- Holds search and dialpad fragments -->
+  <FrameLayout
+      android:id="@+id/search_fragment_container"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent"
+      android:layout_below="@+id/toolbar"/>
+
+  <!-- MainToolbar -->
+  <include
+      android:id="@+id/toolbar"
+      layout="@layout/toolbar_layout"/>
+
   <android.support.design.widget.FloatingActionButton
       android:id="@+id/fab"
       android:layout_width="wrap_content"
diff --git a/java/com/android/dialer/main/impl/res/values/strings.xml b/java/com/android/dialer/main/impl/res/values/strings.xml
index ff136e8..f530fa2 100644
--- a/java/com/android/dialer/main/impl/res/values/strings.xml
+++ b/java/com/android/dialer/main/impl/res/values/strings.xml
@@ -22,6 +22,9 @@
        TODO(38502365): Remove this once we're ready to launch the new UI. -->
   <string name="nui_shortcut_name">Phone NUI</string>
 
+  <!-- Title for the activity that dials the phone, when launched directly into the dialpad -->
+  <string name="dialpad_activity_title">Phone Keypad</string>
+
   <!-- The description text for the call log tab. -->
   <string name="main_call_history_tab_description" tools:ignore="UnusedResources">Call history</string>
 
diff --git a/java/com/android/dialer/main/impl/res/values/styles.xml b/java/com/android/dialer/main/impl/res/values/styles.xml
index f94897a..26f880d 100644
--- a/java/com/android/dialer/main/impl/res/values/styles.xml
+++ b/java/com/android/dialer/main/impl/res/values/styles.xml
@@ -19,5 +19,8 @@
     <item name="android:colorPrimary">@color/dialtacts_theme_color</item>
     <item name="android:colorPrimaryDark">@color/dialer_theme_color_dark</item>
     <item name="android:colorAccent">@color/dialer_secondary_color</item>
+
+    <!-- Theme needed for DialpadFragment -->
+    <item name="dialpad_style">@style/Dialpad.Light</item>
   </style>
 </resources>
diff --git a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java
index 6d9b7da..b322172 100644
--- a/java/com/android/dialer/main/impl/toolbar/MainToolbar.java
+++ b/java/com/android/dialer/main/impl/toolbar/MainToolbar.java
@@ -17,16 +17,24 @@
 package com.android.dialer.main.impl.toolbar;
 
 import android.content.Context;
+import android.support.annotation.VisibleForTesting;
 import android.support.v7.widget.PopupMenu.OnMenuItemClickListener;
 import android.support.v7.widget.Toolbar;
 import android.util.AttributeSet;
 import android.view.MenuItem;
+import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.ImageButton;
+import com.android.dialer.common.Assert;
 
 /** Toolbar for {@link com.android.dialer.main.impl.MainActivity}. */
 public final class MainToolbar extends Toolbar implements OnMenuItemClickListener {
 
+  private static final int SLIDE_DURATION = 300;
+  private static final AccelerateDecelerateInterpolator SLIDE_INTERPOLATOR =
+      new AccelerateDecelerateInterpolator();
+
   private SearchBarListener listener;
+  private boolean isSlideUp;
 
   public MainToolbar(Context context, AttributeSet attrs) {
     super(context, attrs);
@@ -57,4 +65,35 @@
     this.listener = listener;
     ((SearchBarView) findViewById(R.id.search_view_container)).setSearchBarListener(listener);
   }
+
+  /** Slides the toolbar up and off the screen. */
+  public void slideUp(boolean animate) {
+    Assert.checkArgument(!isSlideUp);
+    isSlideUp = true;
+    animate()
+        .translationY(-getHeight())
+        .setDuration(animate ? SLIDE_DURATION : 0)
+        .setInterpolator(SLIDE_INTERPOLATOR)
+        .start();
+  }
+
+  /**
+   * Slides the toolbar down and back onto the screen.
+   *
+   * @param animate
+   */
+  public void slideDown(boolean animate) {
+    Assert.checkArgument(isSlideUp);
+    isSlideUp = false;
+    animate()
+        .translationY(0)
+        .setDuration(animate ? SLIDE_DURATION : 0)
+        .setInterpolator(SLIDE_INTERPOLATOR)
+        .start();
+  }
+
+  @VisibleForTesting
+  public boolean isSlideUp() {
+    return isSlideUp;
+  }
 }