Merge "Fix NPE issue with items that don't have a subtitle (happens with Exchange)"
diff --git a/res/layout-w470dp/contact_detail_fragment.xml b/res/layout-w470dp/contact_detail_fragment.xml
index 17cbc2d..5a48583 100644
--- a/res/layout-w470dp/contact_detail_fragment.xml
+++ b/res/layout-w470dp/contact_detail_fragment.xml
@@ -14,17 +14,28 @@
      limitations under the License.
 -->
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/contact_detail"
-    android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
+     <!-- "QuickFix"- button (Copy to local contact, add to group) -->
+    <Button
+        android:id="@+id/contact_quick_fix"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentBottom="true"
+        android:layout_marginRight="40dip"
+        android:layout_marginTop="20dip"
+        android:layout_marginBottom="20dip" />
+
     <LinearLayout
         android:orientation="horizontal"
         android:layout_width="match_parent"
-        android:layout_height="0px"
-        android:layout_weight="1" >
+        android:layout_above="@id/contact_quick_fix"
+        android:layout_height="match_parent" >
 
         <ImageView android:id="@+id/photo"
             android:scaleType="centerCrop"
@@ -41,12 +52,11 @@
             android:layout_weight="1"
             android:divider="@null"/>
 
-   </LinearLayout>
+    </LinearLayout>
 
     <ScrollView android:id="@android:id/empty"
         android:layout_width="match_parent"
         android:layout_height="0px"
-        android:layout_weight="1"
         android:visibility="gone">
         <TextView android:id="@+id/emptyText"
             android:layout_width="match_parent"
@@ -60,17 +70,6 @@
             android:lineSpacingMultiplier="0.92"/>
     </ScrollView>
 
-    <!-- "QuickFix"- button (Copy to local contact, add to group) -->
-    <Button
-        android:id="@+id/contact_quick_fix"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:visibility="gone"
-        android:layout_gravity="right"
-        android:layout_marginRight="40dip"
-        android:layout_marginTop="20dip"
-        android:layout_marginBottom="20dip" />
-
     <View
         android:id="@+id/alpha_overlay"
         android:layout_width="match_parent"
@@ -89,5 +88,5 @@
         android:layout_alignParentTop="true"
         android:background="@android:color/transparent"
         android:visibility="gone"/>
-</LinearLayout>
+</RelativeLayout>
 
diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java
index 6d28235..5a32d74 100644
--- a/src/com/android/contacts/activities/ActionBarAdapter.java
+++ b/src/com/android/contacts/activities/ActionBarAdapter.java
@@ -31,6 +31,7 @@
 import android.text.TextUtils;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.inputmethod.InputMethodManager;
 import android.widget.SearchView;
 import android.widget.SearchView.OnCloseListener;
 import android.widget.SearchView.OnQueryTextListener;
@@ -112,6 +113,12 @@
         }
         LayoutParams layoutParams = new LayoutParams(searchViewWidth, LayoutParams.WRAP_CONTENT);
         mSearchView = (SearchView) customSearchView.findViewById(R.id.search_view);
+        // Since the {@link SearchView} in this app is "click-to-expand", set the below mode on the
+        // {@link SearchView} so that the magnifying glass icon appears inside the editable text
+        // field. (In the "click-to-expand" search pattern, the user must explicitly expand the
+        // search field and already knows a search is being conducted, so the icon is redundant
+        // and can go away once the user starts typing.)
+        mSearchView.setIconifiedByDefault(true);
         mSearchView.setQueryHint(mContext.getString(R.string.hint_findContacts));
         mSearchView.setOnQueryTextListener(this);
         mSearchView.setOnCloseListener(this);
@@ -265,6 +272,9 @@
     private void update() {
         if (mSearchMode) {
             setFocusOnSearchView();
+            // Since we have the {@link SearchView} in a custom action bar, we must manually handle
+            // expanding the {@link SearchView} when a search is initiated.
+            mSearchView.onActionViewExpanded();
             if (mActionBar.getNavigationMode() != ActionBar.NAVIGATION_MODE_STANDARD) {
                 mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
             }
@@ -287,6 +297,9 @@
                 mTabListener.mIgnoreTabSelected = false;
             }
             mActionBar.setTitle(null);
+            // Since we have the {@link SearchView} in a custom action bar, we must manually handle
+            // collapsing the {@link SearchView} when search mode is exited.
+            mSearchView.onActionViewCollapsed();
             if (mListener != null) {
                 mListener.onAction(Action.STOP_SEARCH_MODE);
                 mListener.onSelectedTabChanged();
@@ -317,6 +330,16 @@
 
     @Override
     public boolean onQueryTextSubmit(String query) {
+        // When the search is "committed" by the user, then hide the keyboard so the user can
+        // more easily browse the list of results.
+        if (mSearchView != null) {
+            InputMethodManager imm = (InputMethodManager) mContext.getSystemService(
+                    Context.INPUT_METHOD_SERVICE);
+            if (imm != null) {
+                imm.hideSoftInputFromWindow(mSearchView.getWindowToken(), 0);
+            }
+            mSearchView.clearFocus();
+        }
         return true;
     }
 
diff --git a/src/com/android/contacts/activities/ContactDetailActivity.java b/src/com/android/contacts/activities/ContactDetailActivity.java
index 6046f5e..02b7bac 100644
--- a/src/com/android/contacts/activities/ContactDetailActivity.java
+++ b/src/com/android/contacts/activities/ContactDetailActivity.java
@@ -65,6 +65,13 @@
 public class ContactDetailActivity extends ContactsActivity {
     private static final String TAG = "ContactDetailActivity";
 
+    /**
+     * Intent key for a boolean that specifies whether the "up" afforance in this activity should
+     * behave as default (return user back to {@link PeopleActivity}) or whether the activity should
+     * instead be finished.
+     */
+    public static final String INTENT_KEY_IGNORE_DEFAULT_UP_BEHAVIOR = "ignoreDefaultUpBehavior";
+
     private static final String KEY_DETAIL_FRAGMENT_TAG = "detailFragTag";
     private static final String KEY_UPDATES_FRAGMENT_TAG = "updatesFragTag";
 
@@ -72,6 +79,7 @@
 
     private ContactLoader.Result mContactData;
     private Uri mLookupUri;
+    private boolean mIgnoreDefaultUpBehavior;
 
     private ContactLoaderFragment mLoaderFragment;
     private ContactDetailFragment mDetailFragment;
@@ -114,6 +122,9 @@
             return;
         }
 
+        mIgnoreDefaultUpBehavior = getIntent().getBooleanExtra(
+                INTENT_KEY_IGNORE_DEFAULT_UP_BEHAVIOR, false);
+
         setContentView(R.layout.contact_detail_activity);
         mRootView = (ViewGroup) findViewById(R.id.contact_detail_view);
         mInflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@@ -529,6 +540,10 @@
 
         switch (item.getItemId()) {
             case android.R.id.home:
+                if (mIgnoreDefaultUpBehavior) {
+                    finish();
+                    return true;
+                }
                 Intent intent = new Intent(this, PeopleActivity.class);
                 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                 startActivity(intent);
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index 6dc727e..48fd49d 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -961,7 +961,15 @@
             if (PhoneCapabilityTester.isUsingTwoPanes(PeopleActivity.this)) {
                 setupContactDetailFragment(contactLookupUri);
             } else {
-                startActivity(new Intent(Intent.ACTION_VIEW, contactLookupUri));
+                Intent intent = new Intent(Intent.ACTION_VIEW, contactLookupUri);
+                // In search mode, the "up" affordance in the contact detail page should return the
+                // user to the search results, so suppress the normal behavior which would re-launch
+                // {@link PeopleActivity} when the "up" affordance is clicked.
+                if (mActionBarAdapter.isSearchMode()) {
+                    intent.putExtra(ContactDetailActivity.INTENT_KEY_IGNORE_DEFAULT_UP_BEHAVIOR,
+                            true);
+                }
+                startActivity(intent);
             }
         }
 
diff --git a/src/com/android/contacts/group/GroupEditorFragment.java b/src/com/android/contacts/group/GroupEditorFragment.java
index 9a09c77..ccc84a2 100644
--- a/src/com/android/contacts/group/GroupEditorFragment.java
+++ b/src/com/android/contacts/group/GroupEditorFragment.java
@@ -329,6 +329,7 @@
             mAutoCompleteAdapter.setContentResolver(mContentResolver);
             mAutoCompleteAdapter.setAccountType(mAccountType);
             mAutoCompleteAdapter.setAccountName(mAccountName);
+            mAutoCompleteAdapter.setDataSet(mDataSet);
             mAutoCompleteTextView.setAdapter(mAutoCompleteAdapter);
             mAutoCompleteTextView.setOnItemClickListener(new OnItemClickListener() {
                 @Override
@@ -561,11 +562,12 @@
     }
 
     private boolean hasValidGroupName() {
-        return !TextUtils.isEmpty(mGroupNameView.getText());
+        return mGroupNameView != null && !TextUtils.isEmpty(mGroupNameView.getText());
     }
 
     private boolean hasNameChange() {
-        return !mGroupNameView.getText().toString().equals(mOriginalGroupName);
+        return mGroupNameView != null &&
+                !mGroupNameView.getText().toString().equals(mOriginalGroupName);
     }
 
     private boolean hasMembershipChange() {
diff --git a/src/com/android/contacts/group/SuggestedMemberListAdapter.java b/src/com/android/contacts/group/SuggestedMemberListAdapter.java
index 4f4413c..e013665 100644
--- a/src/com/android/contacts/group/SuggestedMemberListAdapter.java
+++ b/src/com/android/contacts/group/SuggestedMemberListAdapter.java
@@ -76,9 +76,9 @@
     private ContentResolver mContentResolver;
     private LayoutInflater mInflater;
 
-    private String mAccountType = "";
-    private String mAccountName = "";
-    private String mDataSet = "";
+    private String mAccountType;
+    private String mAccountName;
+    private String mDataSet;
 
     // TODO: Make this a Map for better performance when we check if a new contact is in the list
     // or not
diff --git a/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java b/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java
index 5acf83b..c984418 100644
--- a/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java
+++ b/tests/src/com/android/contacts/tests/streamitems/StreamItemPopulatorActivity.java
@@ -36,7 +36,6 @@
 import android.widget.Button;
 import android.widget.Toast;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -76,6 +75,14 @@
             "the middle of nowhere"
     };
 
+    private String[] commentStrings = new String[]{
+            "3 retweets",
+            "5 shares",
+            "4 likes",
+            "4 +1s",
+            "<i>24567</i> <font color='blue' size='+1'><b>likes</b></font>"
+    };
+
     // Photos to randomly select from.
     private Integer[] imageIds = new Integer[]{
             R.drawable.android,
@@ -87,6 +94,14 @@
             R.drawable.whiskey
     };
 
+    // Only some photos have actions.
+    private String[] imageStrings = new String[]{
+            "android",
+            "goldengate",
+            "iceland",
+            "japan",
+    };
+
     // The contact ID that was picked.
     private long mContactId = -1;
 
@@ -227,28 +242,53 @@
     }
 
     private ContentValues buildStreamItemValues(String accountType, String accountName) {
+        boolean includeComments = randInt(100) < 30;
+        boolean includeAction = randInt(100) < 30;
         ContentValues values = new ContentValues();
+        String place = pickRandom(placeNames);
         values.put(StreamItems.TEXT,
-                String.format(pickRandom(snippetStrings), pickRandom(placeNames)));
-        values.put(StreamItems.COMMENTS, "");
+                String.format(pickRandom(snippetStrings) , place)
+                + (includeComments ? " [c]" : "")
+                + (includeAction ? " [a]" : ""));
+        if (includeComments) {
+            values.put(StreamItems.COMMENTS, pickRandom(commentStrings));
+        } else {
+            values.put(StreamItems.COMMENTS, "");
+        }
         // Set the timestamp to some point in the past.
         values.put(StreamItems.TIMESTAMP,
                 System.currentTimeMillis() - randInt(360000000));
         values.put(RawContacts.ACCOUNT_TYPE, accountType);
         values.put(RawContacts.ACCOUNT_NAME, accountName);
+        if (includeAction) {
+            values.put(StreamItems.ACTION, Intent.ACTION_VIEW);
+            values.put(StreamItems.ACTION_URI, getGoogleSearchUri(place));
+        }
         return values;
     }
 
     private ContentValues buildStreamItemPhotoValues(int index, String accountType,
             String accountName) {
+        Integer imageIndex = pickRandom(imageIds);
         ContentValues values = new ContentValues();
         values.put(StreamItemPhotos.SORT_INDEX, index);
-        values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(pickRandom(imageIds)));
+        values.put(StreamItemPhotos.PHOTO, loadPhotoFromResource(imageIndex));
         values.put(RawContacts.ACCOUNT_TYPE, accountType);
         values.put(RawContacts.ACCOUNT_NAME, accountName);
+        if (imageIndex < imageStrings.length) {
+            values.put(StreamItemPhotos.ACTION, Intent.ACTION_VIEW);
+            String queryTerm = imageStrings[imageIndex];
+            values.put(StreamItemPhotos.ACTION_URI, getGoogleSearchUri(queryTerm));
+
+        }
         return values;
     }
 
+    /** Returns the URI of the Google search results page for the given query. */
+    private String getGoogleSearchUri(String query) {
+        return "http://www.google.com/search?q=" + query.replace(" ", "+");
+    }
+
     private <T> T pickRandom(T[] from) {
         return from[randInt(from.length)];
     }