am 92be9d34: (-s ours) Reconcile with jb-mr1-release - do not merge

* commit '92be9d34bf220d26f49ed1b328a84d3cad84d21f':
diff --git a/chips/src/com/android/ex/chips/BaseRecipientAdapter.java b/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
index c0cfa19..c981728 100644
--- a/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
+++ b/chips/src/com/android/ex/chips/BaseRecipientAdapter.java
@@ -499,6 +499,8 @@
 
     private final DelayedMessageHandler mDelayedMessageHandler = new DelayedMessageHandler();
 
+    private EntriesUpdatedObserver mEntriesUpdatedObserver;
+
     /**
      * Constructor for email queries.
      */
@@ -706,9 +708,14 @@
         return entries;
     }
 
+    public void registerUpdateObserver(EntriesUpdatedObserver observer) {
+        mEntriesUpdatedObserver = observer;
+    }
+
     /** Resets {@link #mEntries} and notify the event to its parent ListView. */
     private void updateEntries(List<RecipientEntry> newEntries) {
         mEntries = newEntries;
+        mEntriesUpdatedObserver.onChanged(newEntries);
         notifyDataSetChanged();
     }
 
@@ -967,4 +974,12 @@
     protected int getPhotoId() {
         return android.R.id.icon;
     }
+
+    /**
+     * Interface called before the BaseRecipientAdapter updates recipient
+     * results in the popup window.
+     */
+    protected interface EntriesUpdatedObserver {
+        public void onChanged(List<RecipientEntry> entries);
+    }
 }
diff --git a/chips/src/com/android/ex/chips/RecipientEditTextView.java b/chips/src/com/android/ex/chips/RecipientEditTextView.java
index 011b1e9..93ca4e8 100644
--- a/chips/src/com/android/ex/chips/RecipientEditTextView.java
+++ b/chips/src/com/android/ex/chips/RecipientEditTextView.java
@@ -54,6 +54,7 @@
 import android.text.util.Rfc822Tokenizer;
 import android.util.AttributeSet;
 import android.util.Log;
+import android.util.TypedValue;
 import android.util.Patterns;
 import android.view.ActionMode;
 import android.view.ActionMode.Callback;
@@ -72,6 +73,7 @@
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.Button;
+import android.widget.Filterable;
 import android.widget.ListAdapter;
 import android.widget.ListPopupWindow;
 import android.widget.ListView;
@@ -86,8 +88,10 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * RecipientEditTextView is an auto complete text view for use with applications
@@ -194,6 +198,15 @@
 
     private boolean mDragEnabled = false;
 
+    // This pattern comes from android.util.Patterns. It has been tweaked to handle a "1" before
+    // parens, so numbers such as "1 (425) 222-2342" match.
+    private static final Pattern PHONE_PATTERN
+        = Pattern.compile(                                  // sdd = space, dot, or dash
+                "(\\+[0-9]+[\\- \\.]*)?"                    // +<digits><sdd>*
+                + "(1?[ ]*\\([0-9]+\\)[\\- \\.]*)?"         // 1(<digits>)<sdd>*
+                + "([0-9][0-9\\- \\.][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
+
+
     private final Runnable mAddTextWatcher = new Runnable() {
         @Override
         public void run() {
@@ -226,6 +239,10 @@
 
     private int mMaxLines;
 
+    private static int sExcessTopPadding = -1;
+
+    private int mActionBarHeight;
+
     public RecipientEditTextView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setChipDimensions(context, attrs);
@@ -389,6 +406,44 @@
         }
     }
 
+    private int getExcessTopPadding() {
+        if (sExcessTopPadding == -1) {
+            sExcessTopPadding = (int) (mChipHeight + mLineSpacingExtra);
+        }
+        return sExcessTopPadding;
+    }
+
+    public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
+        super.setAdapter(adapter);
+        ((BaseRecipientAdapter) adapter)
+                .registerUpdateObserver(new BaseRecipientAdapter.EntriesUpdatedObserver() {
+                    @Override
+                    public void onChanged(List<RecipientEntry> entries) {
+                        // Scroll the chips field to the top of the screen so
+                        // that the user can see as many results as possible.
+                        if (entries != null && entries.size() > 0) {
+                            scrollBottomIntoView();
+                        }
+                    }
+                });
+    }
+
+    private void scrollBottomIntoView() {
+        if (mScrollView != null && mShouldShrink) {
+            int[] location = new int[2];
+            getLocationOnScreen(location);
+            int height = getHeight();
+            int currentPos = location[1] + height;
+            // Desired position shows at least 1 line of chips below the action
+            // bar. We add excess padding to make sure this is always below other
+            // content.
+            int desiredPos = (int) mChipHeight + mActionBarHeight + getExcessTopPadding();
+            if (currentPos > desiredPos) {
+                mScrollView.scrollBy(0, currentPos - desiredPos);
+            }
+        }
+    }
+
     @Override
     public void performValidation() {
         // Do nothing. Chips handles its own validation.
@@ -427,7 +482,7 @@
                     int whatEnd = mTokenizer.findTokenEnd(text, start);
                     // This token was already tokenized, so skip past the ending token.
                     if (whatEnd < text.length() && text.charAt(whatEnd) == ',') {
-                        whatEnd++;
+                        whatEnd = movePastTerminators(whatEnd);
                     }
                     // In the middle of chip; treat this as an edit
                     // and commit the whole token.
@@ -701,6 +756,11 @@
             mInvalidChipBackground = r.getDrawable(R.drawable.chip_background_invalid);
         }
         mLineSpacingExtra =  context.getResources().getDimension(R.dimen.line_spacing_extra);
+        TypedValue tv = new TypedValue();
+        if (context.getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) {
+            mActionBarHeight = TypedValue.complexToDimensionPixelSize(tv.data, getResources()
+                    .getDisplayMetrics());
+        }
         a.recycle();
     }
 
@@ -897,8 +957,6 @@
         RecipientEntry entry = createTokenizedEntry(token);
         if (entry != null) {
             String destText = createAddressText(entry);
-            // Always leave a blank space at the end of a chip.
-            int textLength = destText.length() - 1;
             SpannableString chipText = new SpannableString(destText);
             int end = getSelectionEnd();
             int start = mTokenizer != null ? mTokenizer.findTokenStart(getText(), end) : 0;
@@ -913,12 +971,11 @@
                             TextUtils.isEmpty(entry.getDisplayName())
                                     || TextUtils.equals(entry.getDisplayName(),
                                             entry.getDestination()));
-                    chipText.setSpan(chip, 0, textLength, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                 }
             } catch (NullPointerException e) {
                 Log.e(TAG, e.getMessage(), e);
             }
-            editable.replace(tokenStart, tokenEnd, chipText);
+            editable.setSpan(chip, tokenStart, tokenEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
             // Add this chip to the list of entries "to replace"
             if (chip != null) {
                 if (mTemporaryRecipients == null) {
@@ -938,7 +995,7 @@
             return false;
         }
 
-        Matcher match = Patterns.PHONE.matcher(number);
+        Matcher match = PHONE_PATTERN.matcher(number);
         return match.matches();
     }
 
@@ -1117,6 +1174,7 @@
             int whatEnd = mTokenizer.findTokenEnd(getText(), start);
             // In the middle of chip; treat this as an edit
             // and commit the whole token.
+            whatEnd = movePastTerminators(whatEnd);
             if (whatEnd != getSelectionEnd()) {
                 handleEdit(start, whatEnd);
                 return true;
@@ -1294,7 +1352,8 @@
      */
     @Override
     protected void performFiltering(CharSequence text, int keyCode) {
-        if (enoughToFilter() && !isCompletedToken(text)) {
+        boolean isCompletedToken = isCompletedToken(text);
+        if (enoughToFilter() && !isCompletedToken) {
             int end = getSelectionEnd();
             int start = mTokenizer.findTokenStart(text, end);
             // If this is a RecipientChip, don't filter
@@ -1304,6 +1363,8 @@
             if (chips != null && chips.length > 0) {
                 return;
             }
+        } else if (isCompletedToken) {
+            return;
         }
         super.performFiltering(text, keyCode);
     }
@@ -1384,7 +1445,7 @@
 
     private void scrollLineIntoView(int line) {
         if (mScrollView != null) {
-            mScrollView.scrollBy(0, calculateOffsetFromBottom(line));
+            mScrollView.smoothScrollBy(0, calculateOffsetFromBottom(line));
         }
     }
 
@@ -1880,11 +1941,17 @@
         if (shouldShowEditableText(currentChip)) {
             CharSequence text = currentChip.getValue();
             Editable editable = getText();
-            removeChip(currentChip);
-            editable.append(text);
+            Spannable spannable = getSpannable();
+            int spanStart = spannable.getSpanStart(currentChip);
+            int spanEnd = spannable.getSpanEnd(currentChip);
+            spannable.removeSpan(currentChip);
+            editable.delete(spanStart, spanEnd);
             setCursorVisible(true);
             setSelection(editable.length());
-            return new RecipientChip(null, RecipientEntry.constructFakeEntry((String) text), -1);
+            editable.append(text);
+            return constructChipSpan(
+                    RecipientEntry.constructFakeEntry((String) text),
+                    getSelectionStart(), true, false);
         } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
             int start = getChipStart(currentChip);
             int end = getChipEnd(currentChip);
@@ -2176,7 +2243,7 @@
         public void onTextChanged(CharSequence s, int start, int before, int count) {
             // This is a delete; check to see if the insertion point is on a space
             // following a chip.
-            if (before > count) {
+            if (before - count == 1) {
                 // If the item deleted is a space, and the thing before the
                 // space is a chip, delete the entire span.
                 int selStart = getSelectionStart();
@@ -2195,8 +2262,6 @@
                     editable.delete(tokenStart, tokenEnd);
                     getSpannable().removeSpan(repl[0]);
                 }
-            } else if (count > before) {
-                scrollBottomIntoView();
             }
         }
 
@@ -2206,12 +2271,6 @@
         }
     }
 
-    private void scrollBottomIntoView() {
-        if (mScrollView != null) {
-            mScrollView.scrollBy(0, (int)(getLineCount() * mChipHeight));
-        }
-    }
-
     /**
      * Handles pasting a {@link ClipData} to this {@link RecipientEditTextView}.
      */
@@ -2391,11 +2450,11 @@
                                 end = oldText.getSpanEnd(chip);
                                 oldText.removeSpan(chip);
                                 RecipientChip replacement = replacements.get(i);
-                                // Trim any whitespace, as we will already have
-                                // it added if these are replacement chips.
+                                // Make sure we always have just 1 space at the
+                                // end to separate this chip from the next chip.
                                 SpannableString displayText = new SpannableString(
-                                        createAddressText(replacement.getEntry()).trim());
-                                displayText.setSpan(replacement, 0, displayText.length(),
+                                        createAddressText(replacement.getEntry()).trim() + " ");
+                                displayText.setSpan(replacement, 0, displayText.length()-1,
                                         Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                                 // Replace the old text we found with with the new display text,
                                 // which now may also contain the display name of the recipient.
diff --git a/photoviewer/res/values/dimen.xml b/photoviewer/res/values/dimen.xml
index c1b8b90..754c99b 100644
--- a/photoviewer/res/values/dimen.xml
+++ b/photoviewer/res/values/dimen.xml
@@ -21,4 +21,5 @@
     <dimen name="photo_crop_stroke_width">1dip</dimen>
     <dimen name="photo_preview_size">200dip</dimen>
     <dimen name="retry_button_size">48dip</dimen>
+    <dimen name="photo_page_margin">32dip</dimen>
 </resources>
diff --git a/photoviewer/src/com/android/ex/photo/PhotoViewActivity.java b/photoviewer/src/com/android/ex/photo/PhotoViewActivity.java
index 2a24e1f..6f126b4 100644
--- a/photoviewer/src/com/android/ex/photo/PhotoViewActivity.java
+++ b/photoviewer/src/com/android/ex/photo/PhotoViewActivity.java
@@ -25,6 +25,7 @@
 import android.app.LoaderManager.LoaderCallbacks;
 import android.content.Intent;
 import android.content.Loader;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Build;
@@ -183,18 +184,19 @@
         // Create the adapter and add the view pager
         mAdapter = new PhotoPagerAdapter(this, getFragmentManager(), null);
 
+        final Resources resources = getResources();
         mViewPager = (PhotoViewPager) findViewById(R.id.photo_view_pager);
         mViewPager.setAdapter(mAdapter);
         mViewPager.setOnPageChangeListener(this);
         mViewPager.setOnInterceptTouchListener(this);
+        mViewPager.setPageMargin(resources.getDimensionPixelSize(R.dimen.photo_page_margin));
 
         // Kick off the loader
         getLoaderManager().initLoader(LOADER_PHOTO_LIST, null, this);
 
         final ActionBar actionBar = getActionBar();
         actionBar.setDisplayHomeAsUpEnabled(true);
-        mActionBarHideDelayTime = getResources().getInteger(
-                R.integer.action_bar_delay_time_in_millis);
+        mActionBarHideDelayTime = resources.getInteger(R.integer.action_bar_delay_time_in_millis);
         actionBar.addOnMenuVisibilityListener(this);
         actionBar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
     }