Merge "Delete dialer resources moved into common"
diff --git a/res/layout/call_log_list_item.xml b/res/layout/call_log_list_item.xml
index b36101e..ff6b7ba 100644
--- a/res/layout/call_log_list_item.xml
+++ b/res/layout/call_log_list_item.xml
@@ -21,6 +21,7 @@
     android:layout_height="wrap_content"
     android:id="@+id/call_log_list_item"
     android:orientation="vertical"
+    android:clipChildren="false"
 >
     <!-- Day group heading. Used to show a "today", "yesterday", "last week" or "other" heading
          above a group of call log entries. -->
diff --git a/res/layout/call_log_list_item_actions.xml b/res/layout/call_log_list_item_actions.xml
index eb4aea2..d4e98d0 100644
--- a/res/layout/call_log_list_item_actions.xml
+++ b/res/layout/call_log_list_item_actions.xml
@@ -42,7 +42,8 @@
             android:textSize="@dimen/call_log_secondary_text_size"
             android:nextFocusLeft="@+id/primary_action_view"
             android:nextFocusRight="@+id/voicemail_action"
-            android:focusable="true"/>
+            android:focusable="true"
+            android:singleLine="true"/>
         <TextView
             android:id="@+id/voicemail_action"
             android:background="@drawable/action_button_background"
@@ -57,7 +58,8 @@
             android:textSize="@dimen/call_log_secondary_text_size"
             android:nextFocusLeft="@+id/call_back_action"
             android:nextFocusRight="@+id/delete_action"
-            android:focusable="true"/>
+            android:focusable="true"
+            android:singleLine="true"/>
         <TextView
             android:id="@+id/delete_action"
             android:background="@drawable/action_button_background"
@@ -71,7 +73,24 @@
             android:textColor="@color/call_log_action_delete_text"
             android:textSize="@dimen/call_log_secondary_text_size"
             android:nextFocusLeft="@+id/voicemail_action"
+            android:nextFocusRight="@+id/details_action"
+            android:focusable="true"
+            android:singleLine="true"/>
+        <TextView
+            android:id="@+id/details_action"
+            android:background="@drawable/action_button_background"
+            android:fontFamily="sans-serif-medium"
+            android:gravity="center"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:paddingLeft="@dimen/call_log_action_horizontal_padding"
+            android:paddingRight="@dimen/call_log_action_horizontal_padding"
+            android:text="@string/call_log_action_details"
+            android:textColor="?attr/call_log_secondary_text_color"
+            android:textSize="@dimen/call_log_secondary_text_size"
+            android:nextFocusLeft="@+id/delete_action"
             android:nextFocusRight="@+id/primary_action_view"
-            android:focusable="true"/>
+            android:focusable="true"
+            android:singleLine="true"/>
     </LinearLayout>
 </FrameLayout>
diff --git a/res/layout/search_edittext.xml b/res/layout/search_edittext.xml
index 236d2bf..f44cc0b 100644
--- a/res/layout/search_edittext.xml
+++ b/res/layout/search_edittext.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout
+<view class="com.android.dialer.widget.SearchEditTextLayout"
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
@@ -45,4 +45,4 @@
         android:clickable="true"
         android:contentDescription="@string/description_start_voice_search"
         android:background="?android:attr/selectableItemBackground" />
-</LinearLayout>
\ No newline at end of file
+</view>
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index f734aae..23f639f 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -31,4 +31,6 @@
         <attr name="call_log_voicemail_status_action_text_color" format="color" />
     </declare-styleable>
 
+    <declare-styleable name="SearchEditTextLayout" />
+
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9c6eb68..0f66131 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -429,13 +429,6 @@
     -->
     <string name="description_contact_details">Contact details for <xliff:g id="nameOrNumber">%1$s</xliff:g></string>
 
-    <!-- String describing the button to access call details in the call log.
-
-        Note: AccessibilityServices use this attribute to announce what the view represents.
-              This is especially valuable for views without textual representation like ImageView.
-    -->
-    <string name="description_call_details">Call details</string>
-
     <!-- String indicating a call log entry has an associated voicemail.
 
     Note: AccessibilityServices use this attribute to announce what the view represents.
@@ -680,6 +673,12 @@
          [CHAR LIMIT=30] -->
     <string name="call_log_action_voicemail">LISTEN</string>
 
+    <!-- Button text for the "DETAILS" button displayed underneath an entry in the call log.
+         Tapping navigates the user to the call details screen where the user can view details for
+         the call log entry.
+         [CHAR LIMIT=30] -->
+    <string name="call_log_action_details">DETAILS</string>
+
     <!-- String describing an incoming missed call entry in the call log.
          Note: AccessibilityServices uses this attribute to announce what the view represents.
          [CHAR LIMIT=NONE] -->
@@ -714,6 +713,12 @@
          [CHAR LIMIT=NONE] -->
     <string name="description_delete_action">Delete call log entry for <xliff:g id="nameOrNumber" example="John Smith">%1$s</xliff:g></string>
 
+    <!-- String describing the "details" action for an entry in the call log.  The details action
+         displays the call details screen for an entry in the call log.  This shows the calls to
+         and from the specified number associated with the call log entry.
+         [CHAR LIMIT=NONE] -->
+    <string name="description_details_action">Call details for <xliff:g id="nameOrNumber" example="John Smith">%1$s</xliff:g></string>
+
     <!-- Toast message which appears when a call log entry is deleted.
          [CHAR LIMIT=NONE] -->
     <string name="toast_entry_removed">Call log entry deleted.</string>
diff --git a/src/com/android/dialer/DialtactsActivity.java b/src/com/android/dialer/DialtactsActivity.java
index 5717875..8fa4ae6 100644
--- a/src/com/android/dialer/DialtactsActivity.java
+++ b/src/com/android/dialer/DialtactsActivity.java
@@ -40,12 +40,13 @@
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
-import android.util.AttributeSet;
 import android.util.Log;
 import android.view.DragEvent;
+import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnDragListener;
 import android.view.animation.AccelerateInterpolator;
@@ -84,6 +85,7 @@
 import com.android.dialer.list.RemoveView;
 import com.android.dialer.list.SearchFragment;
 import com.android.dialer.list.SmartDialSearchFragment;
+import com.android.dialer.widget.SearchEditTextLayout;
 import com.android.dialerbind.DatabaseHelperManager;
 import com.android.internal.telephony.ITelephony;
 
@@ -99,7 +101,7 @@
         DialpadFragment.HostInterface,
         ListsFragment.HostInterface,
         SpeedDialFragment.HostInterface,
-        OnDragDropListener, View.OnLongClickListener,
+        OnDragDropListener,
         OnPhoneNumberPickerActionListener,
         ViewPager.OnPageChangeListener {
     private static final String TAG = "DialtactsActivity";
@@ -160,13 +162,12 @@
     private View mFloatingActionButtonContainer;
     private ImageButton mFloatingActionButton;
 
-    private View mFragmentsFrame;
-
     private int mActionBarHeight;
     private boolean mInDialpadSearch;
     private boolean mInRegularSearch;
     private boolean mClearSearchOnPause;
     private boolean mIsDialpadShown;
+    private boolean mIsExitingSearch;
 
     /**
      * The position of the currently selected tab in the attached {@link ListsFragment}.
@@ -206,9 +207,6 @@
     private DialerDatabaseHelper mDialerDatabaseHelper;
     private DragDropController mDragDropController;
 
-    private int mDialerBackgroundColor;
-    private int mContactListBackgroundColor;
-
     private class OverflowPopupMenu extends PopupMenu {
         public OverflowPopupMenu(Context context, View anchor) {
             super(context, anchor);
@@ -243,55 +241,79 @@
      * Listener used to send search queries to the phone search fragment.
      */
     private final TextWatcher mPhoneSearchQueryTextListener = new TextWatcher() {
-            @Override
-            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-            }
+        @Override
+        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+        }
 
-            @Override
-            public void onTextChanged(CharSequence s, int start, int before, int count) {
-                final String newText = s.toString();
-                if (newText.equals(mSearchQuery)) {
-                    // If the query hasn't changed (perhaps due to activity being destroyed
-                    // and restored, or user launching the same DIAL intent twice), then there is
-                    // no need to do anything here.
-                    return;
-                }
-                mSearchQuery = newText;
-                if (DEBUG) {
-                    Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText);
-                }
-                final boolean dialpadSearch = mIsDialpadShown;
-
-                // Show search result with non-empty text. Show a bare list otherwise.
-                if (TextUtils.isEmpty(newText) && getInSearchUi()) {
-                    exitSearchUi();
-                    mSearchViewCloseButton.setVisibility(View.GONE);
-                    mVoiceSearchButton.setVisibility(View.VISIBLE);
-                    return;
-                } else if (!TextUtils.isEmpty(newText)) {
-                    final boolean sameSearchMode = (dialpadSearch && mInDialpadSearch) ||
-                            (!dialpadSearch && mInRegularSearch);
-                    if (!sameSearchMode) {
-                        // call enterSearchUi only if we are switching search modes, or entering
-                        // search ui for the first time
-                        enterSearchUi(dialpadSearch, newText);
-                    }
-
-                    if (dialpadSearch && mSmartDialSearchFragment != null) {
-                            mSmartDialSearchFragment.setQueryString(newText, false);
-                    } else if (mRegularSearchFragment != null) {
-                        mRegularSearchFragment.setQueryString(newText, false);
-                    }
-                    mSearchViewCloseButton.setVisibility(View.VISIBLE);
-                    mVoiceSearchButton.setVisibility(View.GONE);
-                    return;
-                }
+        @Override
+        public void onTextChanged(CharSequence s, int start, int before, int count) {
+            final String newText = s.toString();
+            if (newText.equals(mSearchQuery)) {
+                // If the query hasn't changed (perhaps due to activity being destroyed
+                // and restored, or user launching the same DIAL intent twice), then there is
+                // no need to do anything here.
                 return;
             }
-
-            @Override
-            public void afterTextChanged(Editable s) {
+            mSearchQuery = newText;
+            if (DEBUG) {
+                Log.d(TAG, "onTextChange for mSearchView called with new query: " + newText);
             }
+
+            // Show search result with non-empty text. Show a bare list otherwise.
+            final boolean sameSearchMode = (mIsDialpadShown && mInDialpadSearch) ||
+                    (!mIsDialpadShown && mInRegularSearch);
+            if (!sameSearchMode) {
+                // call enterSearchUi only if we are switching search modes, or entering
+                // search ui for the first time
+                enterSearchUi(mIsDialpadShown, mSearchQuery);
+            }
+
+            if (mIsDialpadShown && mSmartDialSearchFragment != null) {
+                mSmartDialSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
+            } else if (mRegularSearchFragment != null) {
+                mRegularSearchFragment.setQueryString(mSearchQuery, false /* delaySelection */);
+            }
+
+            if (TextUtils.isEmpty(newText)) {
+                mSearchViewCloseButton.setVisibility(View.GONE);
+                mVoiceSearchButton.setVisibility(View.VISIBLE);
+            } else {
+                mSearchViewCloseButton.setVisibility(View.VISIBLE);
+                mVoiceSearchButton.setVisibility(View.GONE);
+            }
+        }
+
+        @Override
+        public void afterTextChanged(Editable s) {
+        }
+    };
+
+
+    /**
+     * Open the search UI when the user touches the search text view.
+     */
+    private final View.OnTouchListener mSearchViewOnTouchListener = new View.OnTouchListener() {
+        @Override
+        public boolean onTouch(View v, MotionEvent event) {
+            if (!isInSearchUi()) {
+                enterSearchUi(false /* smartDialSearch */, mSearchView.getText().toString());
+            }
+            return false;
+        }
+    };
+
+    /**
+     * If the search term is empty and the user closes the soft keyboard, close the search UI.
+     */
+    private final View.OnKeyListener mSearchEditTextLayoutListener = new View.OnKeyListener() {
+        @Override
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN &&
+                    TextUtils.isEmpty(mSearchView.getText().toString())) {
+                onBackPressed();
+            }
+            return false;
+        }
     };
 
     @Override
@@ -306,12 +328,15 @@
         actionBar.setCustomView(R.layout.search_edittext);
         actionBar.setDisplayShowCustomEnabled(true);
 
-        final View customView = actionBar.getCustomView();
+        SearchEditTextLayout actionBarView = (SearchEditTextLayout) actionBar.getCustomView();
+        actionBarView.setPreImeKeyListener(mSearchEditTextLayoutListener);
 
-        mSearchView = (EditText) customView.findViewById(R.id.search_view);
+        mSearchView = (EditText) actionBarView.findViewById(R.id.search_view);
         mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
+        mSearchView.setOnTouchListener(mSearchViewOnTouchListener);
 
-        mSearchViewCloseButton = customView.findViewById(R.id.search_close_button);
+
+        mSearchViewCloseButton = actionBarView.findViewById(R.id.search_close_button);
         mSearchViewCloseButton.setOnClickListener(this);
 
         final TypedArray styledAttributes = getTheme().obtainStyledAttributes(
@@ -337,12 +362,6 @@
         parentLayout.getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING);
         parentLayout.setOnDragListener(new LayoutOnDragListener());
 
-        mDialerBackgroundColor = getResources().getColor(R.color.background_dialer_light);
-        mContactListBackgroundColor =
-                getResources().getColor(R.color.contact_list_background_color);
-
-        mFragmentsFrame = findViewById(R.id.dialtacts_frame);
-
         mFloatingActionButtonContainer = findViewById(R.id.floating_action_button_container);
         ViewUtil.setupFloatingActionButton(mFloatingActionButtonContainer, getResources());
 
@@ -397,15 +416,9 @@
         } else if (fragment instanceof SmartDialSearchFragment) {
             mSmartDialSearchFragment = (SmartDialSearchFragment) fragment;
             mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this);
-            if (mFragmentsFrame != null) {
-                mFragmentsFrame.setAlpha(1.0f);
-            }
         } else if (fragment instanceof SearchFragment) {
             mRegularSearchFragment = (RegularSearchFragment) fragment;
             mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this);
-            if (mFragmentsFrame != null) {
-                mFragmentsFrame.setAlpha(1.0f);
-            }
         } else if (fragment instanceof ListsFragment) {
             mListsFragment = (ListsFragment) fragment;
             mListsFragment.addOnPageChangeListener(this);
@@ -493,22 +506,6 @@
     }
 
     @Override
-    public boolean onLongClick(View view) {
-        switch (view.getId()) {
-            case R.id.floating_action_button:
-                if (mIsDialpadShown) {
-                    // Dial button was pressed; tell the Dialpad fragment
-                    mDialpadFragment.dialButtonPressed();
-                    return true;  // Consume the event
-                }
-            default:
-                Log.wtf(TAG, "Unexpected onClick event from " + view);
-                break;
-        }
-        return false;
-    }
-
-    @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == ACTIVITY_REQUEST_CODE_VOICE_SEARCH) {
             if (resultCode == RESULT_OK) {
@@ -542,6 +539,10 @@
         final FragmentTransaction ft = getFragmentManager().beginTransaction();
         ft.show(mDialpadFragment);
         ft.commit();
+
+        if (!isInSearchUi()) {
+            enterSearchUi(true /* isSmartDial */, mSearchQuery);
+        }
     }
 
     /**
@@ -556,12 +557,6 @@
             mDialpadFragment.setYFraction(0);
         }
 
-        if (mListsFragment != null && mListsFragment.isResumed() && mListsFragment.isVisible()) {
-            // If the favorites fragment is showing, fade to blank.
-            mFragmentsFrame.animate().alpha(0.0f);
-            parentLayout.setBackgroundColor(mContactListBackgroundColor);
-        }
-
         updateSearchFragmentPosition();
         getActionBar().hide();
     }
@@ -598,13 +593,8 @@
             commitDialpadFragmentHide();
         }
 
-        if (mListsFragment != null && mListsFragment.isVisible()) {
-            mFragmentsFrame.animate().alpha(1.0f);
-            parentLayout.setBackgroundColor(mDialerBackgroundColor);
-        }
-
         updateSearchFragmentPosition();
-        getActionBar().show();
+        mListsFragment.maybeShowActionBar();
     }
 
     /**
@@ -630,7 +620,7 @@
         }
     }
 
-    private boolean getInSearchUi() {
+    private boolean isInSearchUi() {
         return mInDialpadSearch || mInRegularSearch;
     }
 
@@ -763,7 +753,7 @@
      * Shows the search fragment
      */
     private void enterSearchUi(boolean smartDialSearch, String query) {
-        if (getFragmentManager().isDestroyed()) {
+        if (getFragmentManager().isDestroyed() || mIsExitingSearch) {
             // Weird race condition where fragment is doing work after the activity is destroyed
             // due to talkback being on (b/10209937). Just return since we can't do any
             // constructive here.
@@ -775,8 +765,6 @@
         }
 
         final FragmentTransaction transaction = getFragmentManager().beginTransaction();
-
-        SearchFragment fragment;
         if (mInDialpadSearch && mSmartDialSearchFragment != null) {
             transaction.remove(mSmartDialSearchFragment);
         } else if (mInRegularSearch && mRegularSearchFragment != null) {
@@ -792,20 +780,26 @@
         mInDialpadSearch = smartDialSearch;
         mInRegularSearch = !smartDialSearch;
 
-        fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag);
+        SearchFragment fragment = (SearchFragment) getFragmentManager().findFragmentByTag(tag);
+        transaction.setCustomAnimations(android.R.animator.fade_in, 0);
         if (fragment == null) {
             if (smartDialSearch) {
                 fragment = new SmartDialSearchFragment();
             } else {
                 fragment = new RegularSearchFragment();
             }
+            transaction.add(R.id.dialtacts_frame, fragment, tag);
+        } else {
+            transaction.show(fragment);
         }
+
         // DialtactsActivity will provide the options menu
         fragment.setHasOptionsMenu(false);
-        transaction.replace(R.id.dialtacts_frame, fragment, tag);
-        transaction.addToBackStack(null);
-        fragment.setQueryString(query, false);
+        fragment.setShowEmptyListForNullQuery(true);
+        fragment.setQueryString(query, false /* delaySelection */);
         transaction.commit();
+
+        mListsFragment.getView().animate().alpha(0).withLayer();
     }
 
     /**
@@ -816,14 +810,27 @@
         if (getFragmentManager().isDestroyed()) {
             return;
         }
-        // Go all the way back to the favorites fragment, regardless of how many times we
-        // transitioned between search fragments
-        getFragmentManager().popBackStack(0, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+
+        mIsExitingSearch = true;
+
+        mSearchView.setText(null);
+        mDialpadFragment.clearDialpad();
         setNotInSearchUi();
 
-        if (mIsDialpadShown) {
-            mFragmentsFrame.setAlpha(0);
+        final FragmentTransaction transaction = getFragmentManager().beginTransaction();
+        transaction.setCustomAnimations(0, android.R.animator.fade_out);
+
+        if (mSmartDialSearchFragment != null) {
+            transaction.remove(mSmartDialSearchFragment);
         }
+        if (mRegularSearchFragment != null) {
+            transaction.remove(mRegularSearchFragment);
+        }
+        transaction.commit();
+
+        mListsFragment.getView().animate().alpha(1).withLayer();
+
+        mIsExitingSearch = false;
     }
 
     /** Returns an Intent to launch Call Settings screen */
@@ -837,10 +844,12 @@
     @Override
     public void onBackPressed() {
         if (mIsDialpadShown) {
+            if (TextUtils.isEmpty(mSearchQuery)) {
+                exitSearchUi();
+            }
             hideDialpadFragment(true, false);
-        } else if (getInSearchUi()) {
-            mSearchView.setText(null);
-            mDialpadFragment.clearDialpad();
+        } else if (isInSearchUi()) {
+            exitSearchUi();
         } else {
             super.onBackPressed();
         }
@@ -883,7 +892,7 @@
 
     @Override
     public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount,
-            int totalItemCount) {
+                                     int totalItemCount) {
         // TODO: No-op for now. This should eventually show/hide the actionBar based on
         // interactions with the ListsFragments.
     }
@@ -962,7 +971,7 @@
         // Specify call-origin so that users will see the previous tab instead of
         // CallLog screen (search UI will be automatically exited).
         PhoneNumberInteraction.startInteractionForPhoneCall(
-            DialtactsActivity.this, dataUri, getCallOrigin());
+                DialtactsActivity.this, dataUri, getCallOrigin());
         mClearSearchOnPause = true;
     }
 
diff --git a/src/com/android/dialer/calllog/CallLogAdapter.java b/src/com/android/dialer/calllog/CallLogAdapter.java
index c4389ad..e1c0acf 100644
--- a/src/com/android/dialer/calllog/CallLogAdapter.java
+++ b/src/com/android/dialer/calllog/CallLogAdapter.java
@@ -641,7 +641,7 @@
         views.callType = callType;
         views.voicemailUri = c.getString(CallLogQuery.VOICEMAIL_URI);
         // Stash away the Ids of the calls so that we can support deleting a row in the call log.
-        views.callIds = getCallIds(c, rowId, count);
+        views.callIds = getCallIds(c, count);
 
         final ContactInfo cachedContactInfo = getContactInfoFromCallLog(c);
 
@@ -857,7 +857,8 @@
             // block and block further attempts to set focus.
             boolean focused = views.callBackButtonView.requestAccessibilityFocus() ||
                     views.voicemailButtonView.requestAccessibilityFocus() ||
-                    views.deleteButtonView.requestAccessibilityFocus();
+                    views.deleteButtonView.requestAccessibilityFocus() ||
+                    views.detailsButtonView.requestAccessibilityFocus();
         } else {
             // When recycling a view, it is possible the actionsView ViewStub was previously
             // inflated so we should hide it in this case.
@@ -894,10 +895,14 @@
             views.voicemailButtonView = (TextView)views.actionsView.findViewById(R.id.voicemail_action);
         }
 
-        if ( views.deleteButtonView == null) {
+        if (views.deleteButtonView == null) {
             views.deleteButtonView = (TextView)views.actionsView.findViewById(R.id.delete_action);
         }
 
+        if (views.detailsButtonView == null) {
+            views.detailsButtonView = (TextView)views.actionsView.findViewById(R.id.details_action);
+        }
+
         bindActionButtons(views);
     }
 
@@ -927,9 +932,18 @@
                     IntentProvider.getPlayVoicemailIntentProvider(
                             views.rowId, views.voicemailUri));
             views.voicemailButtonView.setVisibility(View.VISIBLE);
+
+            views.detailsButtonView.setVisibility(View.GONE);
         } else {
             views.voicemailButtonView.setTag(null);
             views.voicemailButtonView.setVisibility(View.GONE);
+
+            views.detailsButtonView.setOnClickListener(mActionListener);
+            views.detailsButtonView.setTag(
+                    IntentProvider.getCallDetailIntentProvider(
+                            views.rowId, views.callIds, null)
+            );
+
         }
 
         views.deleteButtonView.setOnClickListener(this.mDeleteListener);
@@ -1268,11 +1282,10 @@
      * Retrieves the call Ids represented by the current call log row.
      *
      * @param cursor Call log cursor to retrieve call Ids from.
-     * @param id Id of the first call of the grouping.
      * @param groupSize Number of calls associated with the current call log row.
      * @return Array of call Ids.
      */
-    private long[] getCallIds(final Cursor cursor, final long id, final int groupSize) {
+    private long[] getCallIds(final Cursor cursor, final int groupSize) {
         // We want to restore the position in the cursor at the end.
         int startingPosition = cursor.getPosition();
         long[] ids = new long[groupSize];
diff --git a/src/com/android/dialer/calllog/CallLogListItemHelper.java b/src/com/android/dialer/calllog/CallLogListItemHelper.java
index 976726d..baeb382 100644
--- a/src/com/android/dialer/calllog/CallLogListItemHelper.java
+++ b/src/com/android/dialer/calllog/CallLogListItemHelper.java
@@ -82,6 +82,9 @@
 
         views.deleteButtonView.setContentDescription(
                 mResources.getString(R.string.description_delete_action, views.nameOrNumber));
+
+        views.detailsButtonView.setContentDescription(
+                mResources.getString(R.string.description_details_action, views.nameOrNumber));
     }
 
     /**
diff --git a/src/com/android/dialer/calllog/CallLogListItemViews.java b/src/com/android/dialer/calllog/CallLogListItemViews.java
index 648362e..474b478 100644
--- a/src/com/android/dialer/calllog/CallLogListItemViews.java
+++ b/src/com/android/dialer/calllog/CallLogListItemViews.java
@@ -48,6 +48,8 @@
     public TextView deleteButtonView;
     /** The "voicemail" action button - assigned only when the action section is expanded. */
     public TextView voicemailButtonView;
+    /** The "details" action button - assigned only when the action section is expanded. */
+    public TextView detailsButtonView;
 
     /**
      * The row Id for the first call associated with the call log entry.  Used as a key for the
@@ -122,6 +124,7 @@
         views.callBackButtonView = new TextView(context);
         views.deleteButtonView = new TextView(context);
         views.voicemailButtonView = new TextView(context);
+        views.detailsButtonView = new TextView(context);
         views.actionsView = new View(context);
         return views;
     }
diff --git a/src/com/android/dialer/calllog/IntentProvider.java b/src/com/android/dialer/calllog/IntentProvider.java
index da0c69d..96020be 100644
--- a/src/com/android/dialer/calllog/IntentProvider.java
+++ b/src/com/android/dialer/calllog/IntentProvider.java
@@ -65,39 +65,29 @@
         };
     }
 
+    /**
+     * Retrieves the call details intent provider for an entry in the call log.
+     *
+     * @param id The call ID of the first call in the call group.
+     * @param extraIds The call ID of the other calls grouped together with the call.
+     * @param voicemailUri If call log entry is for a voicemail, the voicemail URI.
+     * @return The call details intent provider.
+     */
     public static IntentProvider getCallDetailIntentProvider(
-            final Cursor cursor, final int position, final long id, final int groupSize) {
+            final long id, final long[] extraIds, final String voicemailUri) {
         return new IntentProvider() {
             @Override
             public Intent getIntent(Context context) {
-                if (cursor.isClosed()) {
-                    // There are reported instances where the cursor is already closed.
-                    // b/10937133
-                    // When causes a crash when it's accessed here.
-                    Log.e(TAG, "getCallDetailIntentProvider() cursor is already closed.");
-                    return null;
-                }
-
-                cursor.moveToPosition(position);
-
                 Intent intent = new Intent(context, CallDetailActivity.class);
                 // Check if the first item is a voicemail.
-                String voicemailUri = cursor.getString(CallLogQuery.VOICEMAIL_URI);
                 if (voicemailUri != null) {
                     intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_URI,
                             Uri.parse(voicemailUri));
                 }
                 intent.putExtra(CallDetailActivity.EXTRA_VOICEMAIL_START_PLAYBACK, false);
 
-                if (groupSize > 1) {
-                    // We want to restore the position in the cursor at the end.
-                    long[] ids = new long[groupSize];
-                    // Copy the ids of the rows in the group.
-                    for (int index = 0; index < groupSize; ++index) {
-                        ids[index] = cursor.getLong(CallLogQuery.ID);
-                        cursor.moveToNext();
-                    }
-                    intent.putExtra(CallDetailActivity.EXTRA_CALL_LOG_IDS, ids);
+                if (extraIds != null && extraIds.length > 0) {
+                    intent.putExtra(CallDetailActivity.EXTRA_CALL_LOG_IDS, extraIds);
                 } else {
                     // If there is a single item, use the direct URI for it.
                     intent.setData(ContentUris.withAppendedId(
diff --git a/src/com/android/dialer/list/ListsFragment.java b/src/com/android/dialer/list/ListsFragment.java
index 18fa531..2507065 100644
--- a/src/com/android/dialer/list/ListsFragment.java
+++ b/src/com/android/dialer/list/ListsFragment.java
@@ -89,6 +89,8 @@
     private CallLogAdapter mCallLogAdapter;
     private CallLogQueryHandler mCallLogQueryHandler;
 
+    private boolean mIsPanelOpen = true;
+
     /**
      * Call shortcuts older than this date (persisted in shared preferences) will not show up in
      * at the top of the screen
@@ -143,15 +145,21 @@
                 final int availableActionBarHeight =
                         Math.min(mActionBar.getHeight(), topPaneHeight);
                 mActionBar.setHideOffset(mActionBar.getHeight() - availableActionBarHeight);
+
+                if (!mActionBar.isShowing()) {
+                    mActionBar.show();
+                }
             }
         }
 
         @Override
         public void onPanelOpened(View panel) {
+            mIsPanelOpen = true;
         }
 
         @Override
         public void onPanelClosed(View panel) {
+            mIsPanelOpen = false;
         }
     };
 
@@ -335,6 +343,15 @@
         }
     }
 
+    public void maybeShowActionBar() {
+        // TODO: Try to show the action bar regardless of whether the panel is open, and then update
+        // the offset to show/hide the action bar, instead of updating the whether the action bar is
+        // shown in onPanelSlide.
+        if (mIsPanelOpen) {
+            mActionBar.show();
+        }
+    }
+
     private void setupPaneLayout(OverlappingPaneLayout paneLayout) {
         // TODO: Remove the notion of a capturable view. The entire view be slideable, once
         // the framework better supports nested scrolling.
diff --git a/src/com/android/dialer/widget/SearchEditTextLayout.java b/src/com/android/dialer/widget/SearchEditTextLayout.java
new file mode 100644
index 0000000..40a4e43
--- /dev/null
+++ b/src/com/android/dialer/widget/SearchEditTextLayout.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.android.dialer.widget;
+
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.LinearLayout;
+
+public class SearchEditTextLayout extends LinearLayout {
+    private OnKeyListener mPreImeKeyListener;
+
+    public SearchEditTextLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public void setPreImeKeyListener(OnKeyListener listener) {
+        mPreImeKeyListener = listener;
+    }
+
+    @Override
+    public boolean dispatchKeyEventPreIme(KeyEvent event) {
+        if (mPreImeKeyListener != null) {
+            if (mPreImeKeyListener.onKey(this, event.getKeyCode(), event)) {
+                return true;
+            }
+        }
+        return super.dispatchKeyEventPreIme(event);
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java b/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java
index cef717f..891f068 100644
--- a/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java
+++ b/tests/src/com/android/dialer/calllog/CallLogGroupBuilderTest.java
@@ -297,5 +297,15 @@
         public void addGroup(int cursorPosition, int size, boolean expanded) {
             groups.add(new GroupSpec(cursorPosition, size, expanded));
         }
+
+        @Override
+        public void setDayGroup(long rowId, int dayGroup) {
+            //No-op
+        }
+
+        @Override
+        public void clearDayGroups() {
+            //No-op
+        }
     }
 }