Merge "Store shortcut action on rotation"
diff --git a/src/com/android/contacts/ContactPhotoManager.java b/src/com/android/contacts/ContactPhotoManager.java
index 7c54e80..f53766d 100644
--- a/src/com/android/contacts/ContactPhotoManager.java
+++ b/src/com/android/contacts/ContactPhotoManager.java
@@ -177,6 +177,14 @@
     public abstract void refreshCache();
 
     /**
+     * Stores the given bitmap directly in the LRU bitmap cache.
+     * @param photoUri The URI of the photo (for future requests).
+     * @param bitmap The bitmap.
+     * @param photoBytes The bytes that were parsed to create the bitmap.
+     */
+    public abstract void cacheBitmap(Uri photoUri, Bitmap bitmap, byte[] photoBytes);
+
+    /**
      * Initiates a background process that over time will fill up cache with
      * preload photos.
      */
@@ -649,6 +657,14 @@
         mBitmapHolderCache.put(key, holder);
     }
 
+    @Override
+    public void cacheBitmap(Uri photoUri, Bitmap bitmap, byte[] photoBytes) {
+        Request request = Request.createFromUri(photoUri, true, false, DEFAULT_AVATER);
+        BitmapHolder holder = new BitmapHolder(photoBytes);
+        mBitmapHolderCache.put(request.getKey(), holder);
+        mBitmapCache.put(request, bitmap);
+    }
+
     /**
      * Populates an array of photo IDs that need to be loaded.
      */
diff --git a/src/com/android/contacts/activities/PhotoSelectionActivity.java b/src/com/android/contacts/activities/PhotoSelectionActivity.java
index 73a85eb..de129cb 100644
--- a/src/com/android/contacts/activities/PhotoSelectionActivity.java
+++ b/src/com/android/contacts/activities/PhotoSelectionActivity.java
@@ -15,6 +15,7 @@
  */
 package com.android.contacts.activities;
 
+import com.android.contacts.ContactPhotoManager;
 import com.android.contacts.ContactSaveService;
 import com.android.contacts.R;
 import com.android.contacts.detail.PhotoSelectionHandler;
@@ -30,6 +31,7 @@
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.view.View;
@@ -58,8 +60,8 @@
 
     private static final String KEY_SUB_ACTIVITY_IN_PROGRESS = "subinprogress";
 
-    /** Intent extra to get the photo bitmap. */
-    public static final String PHOTO_BITMAP = "photo_bitmap";
+    /** Intent extra to get the photo URI. */
+    public static final String PHOTO_URI = "photo_uri";
 
     /** Intent extra to get the entity delta list. */
     public static final String ENTITY_DELTA_LIST = "entity_delta_list";
@@ -80,8 +82,10 @@
     /** Source bounds of the image that was clicked on. */
     private Rect mSourceBounds;
 
-    /** The photo bitmap. */
-    private Bitmap mPhotoBitmap;
+    /**
+     * The photo URI. May be null, in which case the default avatar will be used.
+     */
+    private Uri mPhotoUri;
 
     /** Entity delta list of the contact. */
     private EntityDeltaList mState;
@@ -149,7 +153,7 @@
 
         // Pull data out of the intent.
         final Intent intent = getIntent();
-        mPhotoBitmap = intent.getParcelableExtra(PHOTO_BITMAP);
+        mPhotoUri = intent.getParcelableExtra(PHOTO_URI);
         mState = (EntityDeltaList) intent.getParcelableExtra(ENTITY_DELTA_LIST);
         mIsProfile = intent.getBooleanExtra(IS_PROFILE, false);
         mIsDirectoryContact = intent.getBooleanExtra(IS_DIRECTORY_CONTACT, false);
@@ -193,7 +197,12 @@
     /**
      * Builds a well-formed intent for invoking this activity.
      * @param context The context.
-     * @param photoBitmap The bitmap of the current photo.
+     * @param photoUri The URI of the current photo (may be null, in which case the default
+     *     avatar image will be displayed).
+     * @param photoBitmap The bitmap of the current photo (may be null, in which case the default
+     *     avatar image will be displayed).
+     * @param photoBytes The bytes for the current photo (may be null, in which case the default
+     *     avatar image will be displayed).
      * @param photoBounds The pixel bounds of the current photo.
      * @param delta The entity delta list for the contact.
      * @param isProfile Whether the contact is the user's profile.
@@ -202,11 +211,13 @@
      *     this should be true for phones, and false for tablets).
      * @return An intent that can be used to invoke the photo selection activity.
      */
-    public static Intent buildIntent(Context context, Bitmap photoBitmap, Rect photoBounds,
-            EntityDeltaList delta, boolean isProfile, boolean isDirectoryContact,
-            boolean expandPhotoOnClick) {
+    public static Intent buildIntent(Context context, Uri photoUri, Bitmap photoBitmap,
+            byte[] photoBytes, Rect photoBounds, EntityDeltaList delta, boolean isProfile,
+            boolean isDirectoryContact, boolean expandPhotoOnClick) {
         Intent intent = new Intent(context, PhotoSelectionActivity.class);
-        intent.putExtra(PHOTO_BITMAP, photoBitmap);
+        if (photoUri != null && photoBitmap != null && photoBytes != null) {
+            intent.putExtra(PHOTO_URI, photoUri);
+        }
         intent.setSourceBounds(photoBounds);
         intent.putExtra(ENTITY_DELTA_LIST, (Parcelable) delta);
         intent.putExtra(IS_PROFILE, isProfile);
@@ -233,48 +244,55 @@
     }
 
     private void displayPhoto() {
-        if (mPhotoBitmap != null) {
-            final int[] pos = new int[2];
-            mBackdrop.getLocationOnScreen(pos);
-            LayoutParams layoutParams = new LayoutParams(mSourceBounds.width(),
-                    mSourceBounds.height());
-            mOriginalPos.left = mSourceBounds.left - pos[0];
-            mOriginalPos.top = mSourceBounds.top - pos[1];
-            mOriginalPos.right = mOriginalPos.left + mSourceBounds.width();
-            mOriginalPos.bottom = mOriginalPos.top + mSourceBounds.height();
-            layoutParams.setMargins(mOriginalPos.left, mOriginalPos.top, mOriginalPos.right,
-                    mOriginalPos.bottom);
-            mPhotoStartParams = layoutParams;
-            mPhotoView.setLayoutParams(layoutParams);
-            mPhotoView.requestLayout();
-
-            mPhotoView.setImageBitmap(mPhotoBitmap);
-            mPhotoView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
-                @Override
-                public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
-                    if (mAnimationPending) {
-                        mAnimationPending = false;
-                        PropertyValuesHolder pvhLeft =
-                                PropertyValuesHolder.ofInt("left", mOriginalPos.left, left);
-                        PropertyValuesHolder pvhTop =
-                                PropertyValuesHolder.ofInt("top", mOriginalPos.top, top);
-                        PropertyValuesHolder pvhRight =
-                                PropertyValuesHolder.ofInt("right", mOriginalPos.right, right);
-                        PropertyValuesHolder pvhBottom =
-                                PropertyValuesHolder.ofInt("bottom", mOriginalPos.bottom, bottom);
-                        ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mPhotoView,
-                                pvhLeft, pvhTop, pvhRight, pvhBottom).setDuration(
-                                PHOTO_EXPAND_DURATION);
-                        if (mAnimationListener != null) {
-                            anim.addListener(mAnimationListener);
-                        }
-                        anim.start();
-                    }
-                }
-            });
-            attachPhotoHandler();
+        // Load the photo.
+        if (mPhotoUri != null) {
+            // If we have a URI, the bitmap should be cached directly.
+            ContactPhotoManager.getInstance(this).loadPhoto(mPhotoView, mPhotoUri, true, false);
+        } else {
+            // Fall back to avatar image.
+            mPhotoView.setImageResource(ContactPhotoManager.getDefaultAvatarResId(true, false));
         }
+
+        // Animate the photo view into its end location.
+        final int[] pos = new int[2];
+        mBackdrop.getLocationOnScreen(pos);
+        LayoutParams layoutParams = new LayoutParams(mSourceBounds.width(),
+                mSourceBounds.height());
+        mOriginalPos.left = mSourceBounds.left - pos[0];
+        mOriginalPos.top = mSourceBounds.top - pos[1];
+        mOriginalPos.right = mOriginalPos.left + mSourceBounds.width();
+        mOriginalPos.bottom = mOriginalPos.top + mSourceBounds.height();
+        layoutParams.setMargins(mOriginalPos.left, mOriginalPos.top, mOriginalPos.right,
+                mOriginalPos.bottom);
+        mPhotoStartParams = layoutParams;
+        mPhotoView.setLayoutParams(layoutParams);
+        mPhotoView.requestLayout();
+
+        mPhotoView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+            @Override
+            public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                if (mAnimationPending) {
+                    mAnimationPending = false;
+                    PropertyValuesHolder pvhLeft =
+                            PropertyValuesHolder.ofInt("left", mOriginalPos.left, left);
+                    PropertyValuesHolder pvhTop =
+                            PropertyValuesHolder.ofInt("top", mOriginalPos.top, top);
+                    PropertyValuesHolder pvhRight =
+                            PropertyValuesHolder.ofInt("right", mOriginalPos.right, right);
+                    PropertyValuesHolder pvhBottom =
+                            PropertyValuesHolder.ofInt("bottom", mOriginalPos.bottom, bottom);
+                    ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mPhotoView,
+                            pvhLeft, pvhTop, pvhRight, pvhBottom).setDuration(
+                            PHOTO_EXPAND_DURATION);
+                    if (mAnimationListener != null) {
+                        anim.addListener(mAnimationListener);
+                    }
+                    anim.start();
+                }
+            }
+        });
+        attachPhotoHandler();
     }
 
     private LayoutParams getPhotoEndParams() {
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index 588e6ff..912d7fb 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -29,7 +29,6 @@
 import com.android.contacts.util.StreamItemPhotoEntry;
 import com.google.common.annotations.VisibleForTesting;
 
-import android.app.Activity;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
@@ -45,7 +44,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
-import android.os.Parcelable;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Organization;
 import android.provider.ContactsContract.Data;
@@ -228,7 +226,7 @@
 
         // Set up the photo to display a full-screen photo selection activity when clicked.
         OnClickListener clickListener = new PhotoClickListener(context, contactData, bitmap,
-                expandPhotoOnClick);
+                photo, expandPhotoOnClick);
         photoView.setOnClickListener(clickListener);
         return clickListener;
     }
@@ -238,12 +236,14 @@
         private final Context mContext;
         private final Result mContactData;
         private final Bitmap mPhotoBitmap;
+        private final byte[] mPhotoBytes;
         private final boolean mExpandPhotoOnClick;
         public PhotoClickListener(Context context, Result contactData, Bitmap photoBitmap,
-                boolean expandPhotoOnClick) {
+                byte[] photoBytes, boolean expandPhotoOnClick) {
             mContext = context;
             mContactData = contactData;
             mPhotoBitmap = photoBitmap;
+            mPhotoBytes = photoBytes;
             mExpandPhotoOnClick = expandPhotoOnClick;
         }
 
@@ -266,9 +266,16 @@
             rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
             rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
 
+            Uri photoUri = null;
+            if (mContactData.getPhotoUri() != null) {
+                photoUri = Uri.parse(mContactData.getPhotoUri());
+            }
             Intent photoSelectionIntent = PhotoSelectionActivity.buildIntent(mContext,
-                    mPhotoBitmap, rect, delta, mContactData.isUserProfile(),
+                    photoUri, mPhotoBitmap, mPhotoBytes, rect, delta, mContactData.isUserProfile(),
                     mContactData.isDirectoryEntry(), mExpandPhotoOnClick);
+            // Cache the bitmap directly, so the activity can pull it from the photo manager.
+            ContactPhotoManager.getInstance(mContext).cacheBitmap(photoUri, mPhotoBitmap,
+                    mPhotoBytes);
             mContext.startActivity(photoSelectionIntent);
         }
     }
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index f3d7451..8f77371 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -133,13 +133,6 @@
     private static final String KEY_CONTACT_URI = "contactUri";
     private static final String KEY_LIST_STATE = "liststate";
 
-    // TODO: Make maxLines a field in {@link DataKind}
-    private static final int WEBSITE_MAX_LINES = 1;
-    private static final int SIP_ADDRESS_MAX_LINES= 1;
-    private static final int POSTAL_ADDRESS_MAX_LINES = 10;
-    private static final int GROUP_MAX_LINES = 10;
-    private static final int NOTE_MAX_LINES = 100;
-
     private Context mContext;
     private View mView;
     private OnScrollListener mVerticalScrollListener;
@@ -454,7 +447,7 @@
                 mStaticPhotoContainer.setVisibility(View.VISIBLE);
                 ImageView photoView = (ImageView) mStaticPhotoContainer.findViewById(R.id.photo);
                 OnClickListener listener = ContactDetailDisplayUtils.setPhoto(mContext,
-                        mContactData, photoView, !PhoneCapabilityTester.isUsingTwoPanes(mContext));
+                        mContactData, photoView, false);
                 if (mPhotoTouchOverlay != null) {
                     mPhotoTouchOverlay.setVisibility(View.VISIBLE);
                     mPhotoTouchOverlay.setOnClickListener(listener);
@@ -595,6 +588,7 @@
                 final DetailViewEntry entry = DetailViewEntry.fromValues(mContext, mimeType, kind,
                         dataId, entryValues, mContactData.isDirectoryEntry(),
                         mContactData.getDirectoryId());
+                entry.maxLines = kind.maxLinesForDisplay;
 
                 final boolean hasData = !TextUtils.isEmpty(entry.data);
                 Integer superPrimary = entryValues.getAsInteger(Data.IS_SUPER_PRIMARY);
@@ -651,11 +645,11 @@
                                 mContactData.getDirectoryId());
                         buildImActions(mContext, imEntry, entryValues);
                         imEntry.applyStatus(status, false);
+                        imEntry.maxLines = imKind.maxLinesForDisplay;
                         mImEntries.add(imEntry);
                     }
                 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
                     // Build postal entries
-                    entry.maxLines = POSTAL_ADDRESS_MAX_LINES;
                     entry.intent = StructuredPostalUtils.getViewPostalAddressIntent(entry.data);
                     mPostalEntries.add(entry);
                 } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
@@ -687,12 +681,10 @@
                 } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
                     // Build note entries
                     entry.uri = null;
-                    entry.maxLines = NOTE_MAX_LINES;
                     mNoteEntries.add(entry);
                 } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
                     // Build Website entries
                     entry.uri = null;
-                    entry.maxLines = WEBSITE_MAX_LINES;
                     try {
                         WebAddress webAddress = new WebAddress(entry.data);
                         entry.intent = new Intent(Intent.ACTION_VIEW,
@@ -704,7 +696,6 @@
                 } else if (SipAddress.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) {
                     // Build SipAddress entries
                     entry.uri = null;
-                    entry.maxLines = SIP_ADDRESS_MAX_LINES;
                     if (mHasSip) {
                         entry.intent = ContactsUtils.getCallIntent(
                                 Uri.fromParts(Constants.SCHEME_SIP, entry.data, null));
@@ -768,7 +759,6 @@
             entry.mimetype = GroupMembership.MIMETYPE;
             entry.kind = mContext.getString(R.string.groupsLabel);
             entry.data = sb.toString();
-            entry.maxLines = GROUP_MAX_LINES;
             mGroupEntries.add(entry);
         }
     }
diff --git a/src/com/android/contacts/editor/EventFieldEditorView.java b/src/com/android/contacts/editor/EventFieldEditorView.java
index 4a5a502..475e172 100644
--- a/src/com/android/contacts/editor/EventFieldEditorView.java
+++ b/src/com/android/contacts/editor/EventFieldEditorView.java
@@ -131,7 +131,9 @@
 
     @Override
     public boolean isEmpty() {
-        return TextUtils.isEmpty(mDateView.getText());
+        final EditField editField = getKind().fieldList.get(0);
+        final String column = editField.column;
+        return TextUtils.isEmpty(getEntry().getAsString(column));
     }
 
     @Override
@@ -243,6 +245,7 @@
                 } else {
                     resultString = kind.dateFormatWithYear.format(outCalendar.getTime());
                 }
+
                 onFieldChanged(column, resultString);
                 rebuildDateView();
             }
diff --git a/src/com/android/contacts/editor/PhoneticNameEditorView.java b/src/com/android/contacts/editor/PhoneticNameEditorView.java
index 59545d4..5e47d89 100644
--- a/src/com/android/contacts/editor/PhoneticNameEditorView.java
+++ b/src/com/android/contacts/editor/PhoneticNameEditorView.java
@@ -202,6 +202,11 @@
                 // phonetic name.
                 super.onFieldChanged(column, value);
             }
+        } else {
+            // All fields are always visible, so we don't have to worry about blocking updates
+            // from onRestoreInstanceState() from hidden fields. Always call into the superclass
+            // to update the field and rebuild the underlying phonetic name.
+            super.onFieldChanged(column, value);
         }
     }
 
diff --git a/src/com/android/contacts/model/BaseAccountType.java b/src/com/android/contacts/model/BaseAccountType.java
index cd113eb..4d82ece 100644
--- a/src/com/android/contacts/model/BaseAccountType.java
+++ b/src/com/android/contacts/model/BaseAccountType.java
@@ -79,6 +79,12 @@
     protected static final int FLAGS_RELATION = EditorInfo.TYPE_CLASS_TEXT
             | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS | EditorInfo.TYPE_TEXT_VARIATION_PERSON_NAME;
 
+    // Specify the maximum number of lines that can be used to display various field types.  If no
+    // value is specified for a particular type, we use the default value from {@link DataKind}.
+    protected static final int MAX_LINES_FOR_POSTAL_ADDRESS = 10;
+    protected static final int MAX_LINES_FOR_GROUP = 10;
+    protected static final int MAX_LINES_FOR_NOTE = 100;
+
     private interface Tag {
         static final String DATA_KIND = "DataKind";
         static final String TYPE = "Type";
@@ -323,6 +329,8 @@
                 new EditField(StructuredPostal.FORMATTED_ADDRESS, R.string.postal_address,
                         FLAGS_POSTAL));
 
+        kind.maxLinesForDisplay = MAX_LINES_FOR_POSTAL_ADDRESS;
+
         return kind;
     }
 
@@ -391,6 +399,8 @@
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
 
+        kind.maxLinesForDisplay = MAX_LINES_FOR_NOTE;
+
         return kind;
     }
 
@@ -430,6 +440,8 @@
         kind.fieldList = Lists.newArrayList();
         kind.fieldList.add(new EditField(GroupMembership.GROUP_ROW_ID, -1, -1));
 
+        kind.maxLinesForDisplay = MAX_LINES_FOR_GROUP;
+
         return kind;
     }
 
@@ -1210,6 +1222,7 @@
                             R.string.postal_country, FLAGS_POSTAL).setOptional(true));
                 }
             } else {
+                kind.maxLinesForDisplay= MAX_LINES_FOR_POSTAL_ADDRESS;
                 kind.fieldList.add(
                         new EditField(StructuredPostal.FORMATTED_ADDRESS, R.string.postal_address,
                                 FLAGS_POSTAL));
@@ -1344,6 +1357,7 @@
                     new SimpleInflater(R.string.label_notes), new SimpleInflater(Note.NOTE));
 
             kind.fieldList.add(new EditField(Note.NOTE, R.string.label_notes, FLAGS_NOTE));
+            kind.maxLinesForDisplay = MAX_LINES_FOR_NOTE;
 
             throwIfList(kind);
 
@@ -1417,6 +1431,7 @@
                     R.string.groupsLabel, Weight.GROUP_MEMBERSHIP, -1, null, null);
 
             kind.fieldList.add(new EditField(GroupMembership.GROUP_ROW_ID, -1, -1));
+            kind.maxLinesForDisplay = MAX_LINES_FOR_GROUP;
 
             throwIfList(kind);
 
diff --git a/src/com/android/contacts/model/DataKind.java b/src/com/android/contacts/model/DataKind.java
index 857f3e4..c697e6f 100644
--- a/src/com/android/contacts/model/DataKind.java
+++ b/src/com/android/contacts/model/DataKind.java
@@ -83,8 +83,16 @@
      */
     public SimpleDateFormat dateFormatWithYear;
 
+    /**
+     * The number of lines available for displaying this kind of data in a
+     * {@link ContactDetailFragment} (and possibly elsewhere)
+     * Defaults to 1.
+     */
+    public int maxLinesForDisplay;
+
     public DataKind() {
         editorLayoutResourceId = R.layout.text_fields_editor_view;
+        maxLinesForDisplay = 1;
     }
 
     public DataKind(String mimeType, int titleRes, int weight, boolean editable,
@@ -95,6 +103,7 @@
         this.editable = editable;
         this.typeOverallMax = -1;
         this.editorLayoutResourceId = editorLayoutResourceId;
+        maxLinesForDisplay = 1;
     }
 
     @Override
diff --git a/tests/src/com/android/contacts/tests/mocks/MockContactPhotoManager.java b/tests/src/com/android/contacts/tests/mocks/MockContactPhotoManager.java
index 51c665f..67b7c0c 100644
--- a/tests/src/com/android/contacts/tests/mocks/MockContactPhotoManager.java
+++ b/tests/src/com/android/contacts/tests/mocks/MockContactPhotoManager.java
@@ -18,6 +18,7 @@
 
 import com.android.contacts.ContactPhotoManager;
 
+import android.graphics.Bitmap;
 import android.net.Uri;
 import android.widget.ImageView;
 
@@ -56,6 +57,10 @@
     }
 
     @Override
+    public void cacheBitmap(Uri photoUri, Bitmap bitmap, byte[] photoBytes) {
+    }
+
+    @Override
     public void preloadPhotosInBackground() {
     }
 }