Fine-tuned enlarging of contact photos

When tapping a contact detail photo on any device, the
detail photo will attempt to expand as large as indicated
while still remaining fully on the screen and leaving some
space underneath to show the popup menu options.

If there is no photo, then there will be no expansion
although writable contacts will show a popup menu.

Bug: 6462711
Change-Id: I8f69a1c18dfa10ff0d02b9dbd3ba78043cc7eb4e
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index c53e03f..5037279 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -16,8 +16,6 @@
 <resources>
     <dimen name="account_selector_popup_width">400dip</dimen>
 
-    <dimen name="photo_action_popup_width">400dip</dimen>
-
     <!-- Top position of quick contact. If this is -1, the vertical position is determined
     based on the source of the request -->
     <dimen name="quick_contact_top_position">48dip</dimen>
@@ -82,6 +80,16 @@
     <!-- Width and height of the contact photo on the contact detail page -->
     <dimen name="detail_contact_photo_size">128dip</dimen>
 
+    <!-- Width and height of the expanded contact photo on the contact detail page -->
+    <dimen name="detail_contact_photo_expanded_size">400dip</dimen>
+
+    <!-- This is the minimum amount of space to leave underneath an expanded contact detail
+         photo -->
+    <dimen name="expanded_photo_height_offset">100dip</dimen>
+
+    <!-- Minimum width for the photo action popup options -->
+    <dimen name="photo_action_popup_min_width">300dip</dimen>
+
     <!-- Left and right padding for a contact detail item -->
     <dimen name="detail_item_icon_margin">8dip</dimen>
 
diff --git a/src/com/android/contacts/activities/PhotoSelectionActivity.java b/src/com/android/contacts/activities/PhotoSelectionActivity.java
index 0610bb6..21cf192 100644
--- a/src/com/android/contacts/activities/PhotoSelectionActivity.java
+++ b/src/com/android/contacts/activities/PhotoSelectionActivity.java
@@ -37,7 +37,6 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
-import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.widget.FrameLayout.LayoutParams;
@@ -105,6 +104,15 @@
     /** Whether to animate the photo to an expanded view covering more of the screen. */
     private boolean mExpandPhoto;
 
+    /**
+     * Side length (in pixels) of the expanded photo if to be expanded. Photos are expected to
+     * be square.
+     */
+    private int mExpandedPhotoSize;
+
+    /** Height (in pixels) to leave underneath the expanded photo to show the list popup */
+    private int mHeightOffset;
+
     /** The semi-transparent backdrop. */
     private View mBackdrop;
 
@@ -164,6 +172,12 @@
         mIsDirectoryContact = intent.getBooleanExtra(IS_DIRECTORY_CONTACT, false);
         mExpandPhoto = intent.getBooleanExtra(EXPAND_PHOTO, false);
 
+        // Pull out photo expansion properties from resources
+        mExpandedPhotoSize = getResources().getDimensionPixelSize(
+                R.dimen.detail_contact_photo_expanded_size);
+        mHeightOffset = getResources().getDimensionPixelOffset(
+                R.dimen.expanded_photo_height_offset);
+
         mBackdrop = findViewById(R.id.backdrop);
         mPhotoView = (ImageView) findViewById(R.id.photo);
         mSourceBounds = intent.getSourceBounds();
@@ -188,6 +202,30 @@
         });
     }
 
+    /**
+     * Compute the adjusted expanded photo size to fit within the enclosing view with the same
+     * aspect ratio.
+     * @param enclosingView This is the view that the photo must fit within.
+     * @param heightOffset This is the amount of height to leave open for the photo action popup.
+     */
+    private int getAdjustedExpandedPhotoSize(View enclosingView, int heightOffset) {
+        // pull out the bounds of the backdrop
+        final Rect bounds = new Rect();
+        enclosingView.getDrawingRect(bounds);
+        final int boundsWidth = bounds.width();
+        final int boundsHeight = bounds.height() - heightOffset;
+
+        // ensure that the new expanded photo size can fit within the backdrop
+        final float alpha = Math.min((float) boundsHeight / (float) mExpandedPhotoSize,
+                (float) boundsWidth / (float) mExpandedPhotoSize);
+        if (alpha < 1.0f) {
+            // need to shrink width and height while maintaining aspect ratio
+            return (int) (alpha * mExpandedPhotoSize);
+        } else {
+            return mExpandedPhotoSize;
+        }
+    }
+
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
@@ -279,7 +317,6 @@
 
         // Load the photo.
         int photoWidth = getPhotoEndParams().width;
-        Log.d(TAG, "Photo width: " + photoWidth);
         if (mPhotoUri != null) {
             // If we have a URI, the bitmap should be cached directly.
             ContactPhotoManager.getInstance(this).loadPhoto(mPhotoView, mPhotoUri, photoWidth,
@@ -317,25 +354,32 @@
         attachPhotoHandler();
     }
 
+    /**
+     * This sets the photo's layout params at the end of the animation.
+     * <p>
+     * The scheme is to enlarge the photo to the desired size with the enlarged photo shifted
+     * to the top left of the screen as much as possible while keeping the underlying smaller
+     * photo occluded.
+     */
     private LayoutParams getPhotoEndParams() {
         if (mPhotoEndParams == null) {
             mPhotoEndParams = new LayoutParams(mPhotoStartParams);
             if (mExpandPhoto) {
-                Rect bounds = new Rect();
-                mBackdrop.getDrawingRect(bounds);
-                if (bounds.height() > bounds.width()) {
-                    //Take up full width.
-                    mPhotoEndParams.width = bounds.width();
-                    mPhotoEndParams.height = bounds.width();
-                } else {
-                    // Take up full height, leaving space for the popup.
-                    mPhotoEndParams.height = bounds.height() - 150;
-                    mPhotoEndParams.width = bounds.height() - 150;
+                final int adjustedPhotoSize = getAdjustedExpandedPhotoSize(mBackdrop,
+                        mHeightOffset);
+                int widthDelta = adjustedPhotoSize - mPhotoStartParams.width;
+                int heightDelta = adjustedPhotoSize - mPhotoStartParams.height;
+                if (widthDelta >= 1 || heightDelta >= 1) {
+                    // This is an actual expansion.
+                    mPhotoEndParams.width = adjustedPhotoSize;
+                    mPhotoEndParams.height = adjustedPhotoSize;
+                    mPhotoEndParams.topMargin =
+                            Math.max(mPhotoStartParams.topMargin - heightDelta, 0);
+                    mPhotoEndParams.leftMargin =
+                            Math.max(mPhotoStartParams.leftMargin - widthDelta, 0);
+                    mPhotoEndParams.bottomMargin = 0;
+                    mPhotoEndParams.rightMargin = 0;
                 }
-                mPhotoEndParams.topMargin = 0;
-                mPhotoEndParams.leftMargin = 0;
-                mPhotoEndParams.bottomMargin = mPhotoEndParams.height;
-                mPhotoEndParams.rightMargin = mPhotoEndParams.width;
             }
         }
         return mPhotoEndParams;
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index 29aa51a..1322e6b 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -425,12 +425,14 @@
             // updates or not.
             if (mShowStaticPhoto) {
                 mStaticPhotoContainer.setVisibility(View.VISIBLE);
-                ImageView photoView = (ImageView) mStaticPhotoContainer.findViewById(R.id.photo);
-                OnClickListener listener = mPhotoSetter.setupContactPhotoForClick(
-                        mContext, mContactData, photoView, false);
+                final ImageView photoView = (ImageView) mStaticPhotoContainer.findViewById(
+                        R.id.photo);
+                final boolean expandPhotoOnClick = mContactData.getPhotoUri() != null;
+                final OnClickListener listener = mPhotoSetter.setupContactPhotoForClick(
+                        mContext, mContactData, photoView, expandPhotoOnClick);
                 if (mPhotoTouchOverlay != null) {
                     mPhotoTouchOverlay.setVisibility(View.VISIBLE);
-                    if (mContactData.isWritableContact(mContext)) {
+                    if (expandPhotoOnClick || mContactData.isWritableContact(mContext)) {
                         mPhotoTouchOverlay.setOnClickListener(listener);
                     } else {
                         mPhotoTouchOverlay.setClickable(false);
@@ -1524,8 +1526,8 @@
 
             // Set the photo if it should be displayed
             if (viewCache.photoView != null) {
-                final boolean expandOnClick = !PhoneCapabilityTester.isUsingTwoPanes(mContext);
-                OnClickListener listener = mPhotoSetter.setupContactPhotoForClick(
+                final boolean expandOnClick = mContactData.getPhotoUri() != null;
+                final OnClickListener listener = mPhotoSetter.setupContactPhotoForClick(
                         mContext, mContactData, viewCache.photoView, expandOnClick);
 
                 if (expandOnClick || mContactData.isWritableContact(mContext)) {
diff --git a/src/com/android/contacts/detail/ContactDetailPhotoSetter.java b/src/com/android/contacts/detail/ContactDetailPhotoSetter.java
index b674dc0..dffb37b 100644
--- a/src/com/android/contacts/detail/ContactDetailPhotoSetter.java
+++ b/src/com/android/contacts/detail/ContactDetailPhotoSetter.java
@@ -72,6 +72,7 @@
             final int[] pos = new int[2];
             v.getLocationOnScreen(pos);
 
+            // rect is the bounds (in pixels) of the photo view in screen coordinates
             final Rect rect = new Rect();
             rect.left = (int) (pos[0] * appScale + 0.5f);
             rect.top = (int) (pos[1] * appScale + 0.5f);
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index dd0723d..3929281 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -472,8 +472,8 @@
 
         // TODO: Move this into the {@link CarouselTab} class when the updates
         // fragment code is more finalized.
-        final boolean expandOnClick = !PhoneCapabilityTester.isUsingTwoPanes(mContext);
-        OnClickListener listener = mPhotoSetter.setupContactPhotoForClick(
+        final boolean expandOnClick = contactData.getPhotoUri() != null;
+        final OnClickListener listener = mPhotoSetter.setupContactPhotoForClick(
                 mContext, contactData, mPhotoView, expandOnClick);
 
         if (expandOnClick || contactData.isWritableContact(mContext)) {
diff --git a/src/com/android/contacts/editor/PhotoActionPopup.java b/src/com/android/contacts/editor/PhotoActionPopup.java
index 9744308..a27dd8a 100644
--- a/src/com/android/contacts/editor/PhotoActionPopup.java
+++ b/src/com/android/contacts/editor/PhotoActionPopup.java
@@ -126,10 +126,13 @@
         listPopupWindow.setAnchorView(anchorView);
         listPopupWindow.setAdapter(adapter);
         listPopupWindow.setOnItemClickListener(clickListener);
-        listPopupWindow.setWidth(context.getResources().getDimensionPixelSize(
-                R.dimen.photo_action_popup_width));
         listPopupWindow.setModal(true);
         listPopupWindow.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
+        final int minWidth = context.getResources().getDimensionPixelSize(
+                R.dimen.photo_action_popup_min_width);
+        if (anchorView.getWidth() < minWidth) {
+            listPopupWindow.setWidth(minWidth);
+        }
         return listPopupWindow;
     }