Miscellaneous contact-detail fixes.

Fix bug in carousel "Updates" tab, which would turn black when swiping
it all the way to the left.  This was caused by a small negative alpha
being computed, and some incautious integer arithmetic to transform it
for use in setBackgroundColor().  Fix clamps the alpha value between 0
and 1 (using newly-introduced MoreMath class).

Fix visual glitch where the "Updates" label displays the "pushed" glow
when tapped.

Bug: 6009463
Change-Id: Iee041ee7793f198f1d51ea5e7c5bbcb07b9a41df
diff --git a/src/com/android/contacts/ContactLoader.java b/src/com/android/contacts/ContactLoader.java
index e85f5f5..bc0f3f4 100644
--- a/src/com/android/contacts/ContactLoader.java
+++ b/src/com/android/contacts/ContactLoader.java
@@ -410,6 +410,23 @@
                     && mDirectoryId != Directory.LOCAL_INVISIBLE;
         }
 
+        /**
+         * @return true if this is a contact (not group, etc.) with at least one
+         *         writeable raw-contact, and false otherwise.
+         */
+        public boolean isWritableContact(Context context) {
+            if (isDirectoryEntry()) return false;
+            final AccountTypeManager accountTypes = AccountTypeManager.getInstance(context);
+            for (Entity rawContact : getEntities()) {
+                final ContentValues rawValues = rawContact.getEntityValues();
+                final String accountType = rawValues.getAsString(RawContacts.ACCOUNT_TYPE);
+                final String dataSet = rawValues.getAsString(RawContacts.DATA_SET);
+                final AccountType type = accountTypes.getAccountType(accountType, dataSet);
+                if (type != null && type.areContactsWritable()) return true;
+            }
+            return false;
+        }
+
         public int getDirectoryExportSupport() {
             return mDirectoryExportSupport;
         }
diff --git a/src/com/android/contacts/detail/CarouselTab.java b/src/com/android/contacts/detail/CarouselTab.java
index 9331bed..dc564a9 100644
--- a/src/com/android/contacts/detail/CarouselTab.java
+++ b/src/com/android/contacts/detail/CarouselTab.java
@@ -57,10 +57,7 @@
         super.onFinishInflate();
 
         mLabelView = (TextView) findViewById(R.id.label);
-        mLabelView.setClickable(true);
-
         mLabelBackgroundView = findViewById(R.id.label_background);
-
         mAlphaLayer = findViewById(R.id.alpha_overlay);
         mTouchInterceptLayer = findViewById(R.id.touch_intercept_overlay);
     }
diff --git a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
index 9f37899..319cba5 100644
--- a/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
+++ b/src/com/android/contacts/detail/ContactDetailDisplayUtils.java
@@ -25,6 +25,7 @@
 import com.android.contacts.preference.ContactsPreferences;
 import com.android.contacts.util.ContactBadgeUtil;
 import com.android.contacts.util.HtmlUtils;
+import com.android.contacts.util.MoreMath;
 import com.android.contacts.util.StreamItemEntry;
 import com.android.contacts.util.StreamItemPhotoEntry;
 import com.google.common.annotations.VisibleForTesting;
@@ -616,7 +617,7 @@
         if (view != null) {
             // Convert alpha layer to a black background HEX color with an alpha value for better
             // performance (i.e. use setBackgroundColor() instead of setAlpha())
-            view.setBackgroundColor((int) (alpha * 255) << 24);
+            view.setBackgroundColor((int) (MoreMath.clamp(alpha, 0.0f, 1.0f) * 255) << 24);
         }
     }
 
diff --git a/src/com/android/contacts/detail/ContactDetailFragment.java b/src/com/android/contacts/detail/ContactDetailFragment.java
index b059ef4..d5b04d9 100644
--- a/src/com/android/contacts/detail/ContactDetailFragment.java
+++ b/src/com/android/contacts/detail/ContactDetailFragment.java
@@ -26,6 +26,7 @@
 import com.android.contacts.R;
 import com.android.contacts.TypePrecedence;
 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
+import com.android.contacts.detail.ContactDetailPhotoSetter;
 import com.android.contacts.editor.SelectAccountDialogFragment;
 import com.android.contacts.model.AccountType;
 import com.android.contacts.model.AccountType.EditType;
@@ -40,7 +41,6 @@
 import com.android.contacts.util.Constants;
 import com.android.contacts.util.DataStatus;
 import com.android.contacts.util.DateUtils;
-import com.android.contacts.util.ImageViewDrawableSetter;
 import com.android.contacts.util.PhoneCapabilityTester;
 import com.android.contacts.util.StructuredPostalUtils;
 import com.android.internal.telephony.ITelephony;
@@ -145,7 +145,7 @@
     private Uri mPrimaryPhoneUri = null;
     private ViewEntryDimensions mViewEntryDimensions;
 
-    private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
+    private final ContactDetailPhotoSetter mPhotoSetter = new ContactDetailPhotoSetter();
 
     private Button mQuickFixButton;
     private QuickFix mQuickFix;
diff --git a/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
index 0c3e6ac..f9b057b 100644
--- a/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailFragmentCarousel.java
@@ -226,10 +226,9 @@
     }
 
     private void updateAlphaLayers() {
-        mAboutFragment.setAlphaLayerValue(mLastScrollPosition * MAX_ALPHA /
-                mAllowedHorizontalScrollLength);
-        mUpdatesFragment.setAlphaLayerValue(MAX_ALPHA - mLastScrollPosition * MAX_ALPHA /
-                mAllowedHorizontalScrollLength);
+        float alpha = mLastScrollPosition * MAX_ALPHA / mAllowedHorizontalScrollLength;
+        mAboutFragment.setAlphaLayerValue(alpha);
+        mUpdatesFragment.setAlphaLayerValue(MAX_ALPHA - alpha);
     }
 
     @Override
diff --git a/src/com/android/contacts/detail/ContactDetailLayoutController.java b/src/com/android/contacts/detail/ContactDetailLayoutController.java
index a87f97e..be07e7a 100644
--- a/src/com/android/contacts/detail/ContactDetailLayoutController.java
+++ b/src/com/android/contacts/detail/ContactDetailLayoutController.java
@@ -17,6 +17,7 @@
 package com.android.contacts.detail;
 
 import com.android.contacts.ContactLoader;
+
 import com.android.contacts.NfcHandler;
 import com.android.contacts.R;
 import com.android.contacts.activities.ContactDetailActivity.FragmentKeyListener;
@@ -35,6 +36,7 @@
 import android.os.Bundle;
 import android.support.v4.view.ViewPager;
 import android.support.v4.view.ViewPager.OnPageChangeListener;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
@@ -56,6 +58,9 @@
 
     private final int SINGLE_PANE_FADE_IN_DURATION = 275;
 
+    private static final String TAG = "ContactDetailLayoutController";
+    private static final boolean DEBUG = false;
+
     /**
      * There are 3 possible layouts for the contact detail screen:
      * 1. TWO_COLUMN - Tall and wide screen so the 2 pages can be shown side-by-side
@@ -130,9 +135,13 @@
         // Determine the layout mode based on the presence of certain views in the layout XML.
         if (mViewPager != null) {
             mLayoutMode = LayoutMode.VIEW_PAGER_AND_TAB_CAROUSEL;
+            if (DEBUG) Log.d(TAG, "set layout mode to VIEW_PAGER_AND_TAB_CAROUSEL");
+        } else if (mFragmentCarousel != null) {
+            mLayoutMode = LayoutMode.FRAGMENT_CAROUSEL;
+            if (DEBUG) Log.d(TAG, "set layout mode to FRAGMENT_CAROUSEL");
         } else {
-            mLayoutMode = (mFragmentCarousel != null) ? LayoutMode.FRAGMENT_CAROUSEL :
-                    LayoutMode.TWO_COLUMN;
+            mLayoutMode = LayoutMode.TWO_COLUMN;
+            if (DEBUG) Log.d(TAG, "set layout mode to TWO_COLUMN");
         }
 
         initialize(savedState);
@@ -143,7 +152,7 @@
         mDetailFragment = (ContactDetailFragment) mFragmentManager.findFragmentByTag(
                 ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
         mUpdatesFragment = (ContactDetailUpdatesFragment) mFragmentManager.findFragmentByTag(
-                ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
+                ContactDetailViewPagerAdapter.UPDATES_FRAGMENT_TAG);
 
         // If the detail fragment was found in the {@link FragmentManager} then we don't need to add
         // it again. Otherwise, create the fragments dynamically and remember to add them to the
@@ -190,7 +199,7 @@
                     transaction.add(R.id.about_fragment_container, mDetailFragment,
                             ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
                     transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
-                            ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
+                            ContactDetailViewPagerAdapter.UPDATES_FRAGMENT_TAG);
                     transaction.commitAllowingStateLoss();
                     mFragmentManager.executePendingTransactions();
                 }
@@ -210,7 +219,7 @@
                     transaction.add(R.id.about_fragment_container, mDetailFragment,
                             ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
                     transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
-                            ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
+                            ContactDetailViewPagerAdapter.UPDATES_FRAGMENT_TAG);
                     transaction.commitAllowingStateLoss();
                     mFragmentManager.executePendingTransactions();
                 }
@@ -225,7 +234,7 @@
                     transaction.add(R.id.about_fragment_container, mDetailFragment,
                             ContactDetailViewPagerAdapter.ABOUT_FRAGMENT_TAG);
                     transaction.add(R.id.updates_fragment_container, mUpdatesFragment,
-                            ContactDetailViewPagerAdapter.UPDTES_FRAGMENT_TAG);
+                            ContactDetailViewPagerAdapter.UPDATES_FRAGMENT_TAG);
                     transaction.commitAllowingStateLoss();
                     mFragmentManager.executePendingTransactions();
                 }
diff --git a/src/com/android/contacts/detail/ContactDetailPhotoSetter.java b/src/com/android/contacts/detail/ContactDetailPhotoSetter.java
new file mode 100644
index 0000000..821b441
--- /dev/null
+++ b/src/com/android/contacts/detail/ContactDetailPhotoSetter.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2012 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.contacts.detail;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.net.Uri;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.ImageView;
+
+import com.android.contacts.ContactPhotoManager;
+import com.android.contacts.ContactLoader.Result;
+import com.android.contacts.activities.PhotoSelectionActivity;
+import com.android.contacts.model.EntityDeltaList;
+import com.android.contacts.util.ImageViewDrawableSetter;
+
+/**
+ * Extends superclass with methods specifically for setting the contact-detail
+ * photo.
+ */
+public class ContactDetailPhotoSetter extends ImageViewDrawableSetter {
+    public OnClickListener setupContactPhotoForClick(Context context, Result contactData,
+            ImageView photoView, boolean expandPhotoOnClick) {
+        setTarget(photoView);
+        Bitmap bitmap = setCompressedImage(contactData.getPhotoBinaryData());
+        return setupClickListener(context, contactData, bitmap, expandPhotoOnClick);
+    }
+
+    private static final class PhotoClickListener implements OnClickListener {
+
+        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,
+                byte[] photoBytes, boolean expandPhotoOnClick) {
+            mContext = context;
+            mContactData = contactData;
+            mPhotoBitmap = photoBitmap;
+            mPhotoBytes = photoBytes;
+            mExpandPhotoOnClick = expandPhotoOnClick;
+        }
+
+        @Override
+        public void onClick(View v) {
+            // Assemble the intent.
+            EntityDeltaList delta = EntityDeltaList.fromIterator(
+                    mContactData.getEntities().iterator());
+
+            // Find location and bounds of target view, adjusting based on the
+            // assumed local density.
+            final float appScale =
+                    mContext.getResources().getCompatibilityInfo().applicationScale;
+            final int[] pos = new int[2];
+            v.getLocationOnScreen(pos);
+
+            final Rect rect = new Rect();
+            rect.left = (int) (pos[0] * appScale + 0.5f);
+            rect.top = (int) (pos[1] * appScale + 0.5f);
+            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,
+                    photoUri, mPhotoBitmap, mPhotoBytes, rect, delta, mContactData.isUserProfile(),
+                    mContactData.isDirectoryEntry(), mExpandPhotoOnClick);
+            // Cache the bitmap directly, so the activity can pull it from the
+            // photo manager.
+            if (mPhotoBitmap != null) {
+                ContactPhotoManager.getInstance(mContext).cacheBitmap(
+                        photoUri, mPhotoBitmap, mPhotoBytes);
+            }
+            mContext.startActivity(photoSelectionIntent);
+        }
+    }
+
+    private OnClickListener setupClickListener(Context context, Result contactData, Bitmap bitmap,
+            boolean expandPhotoOnClick) {
+        final ImageView target = getTarget();
+        if (target == null) return null;
+
+        OnClickListener clickListener = new PhotoClickListener(
+                context, contactData, bitmap, getCompressedImage(), expandPhotoOnClick);
+        target.setOnClickListener(clickListener);
+        return clickListener;
+    }
+}
diff --git a/src/com/android/contacts/detail/ContactDetailTabCarousel.java b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
index 008136d..21a2c5b 100644
--- a/src/com/android/contacts/detail/ContactDetailTabCarousel.java
+++ b/src/com/android/contacts/detail/ContactDetailTabCarousel.java
@@ -16,10 +16,11 @@
 
 package com.android.contacts.detail;
 
-import com.android.contacts.ContactLoader;
 import com.android.contacts.R;
+import com.android.contacts.ContactLoader;
+import com.android.contacts.detail.ContactDetailPhotoSetter;
 import com.android.contacts.util.PhoneCapabilityTester;
-import com.android.contacts.util.ImageViewDrawableSetter;
+
 
 import android.content.Context;
 import android.content.res.Resources;
@@ -61,7 +62,7 @@
     private TextView mStatusView;
     private ImageView mStatusPhotoView;
     private OnClickListener mPhotoClickListener;
-    private final ImageViewDrawableSetter mPhotoSetter = new ImageViewDrawableSetter();
+    private final ContactDetailPhotoSetter mPhotoSetter = new ContactDetailPhotoSetter();
 
     private Listener mListener;
 
@@ -301,10 +302,9 @@
     }
 
     private void updateAlphaLayers() {
-        mAboutTab.setAlphaLayerValue(mLastScrollPosition * MAX_ALPHA /
-                mAllowedHorizontalScrollLength);
-        mUpdatesTab.setAlphaLayerValue(MAX_ALPHA - mLastScrollPosition * MAX_ALPHA /
-                mAllowedHorizontalScrollLength);
+        float alpha = mLastScrollPosition * MAX_ALPHA / mAllowedHorizontalScrollLength;
+        mAboutTab.setAlphaLayerValue(alpha);
+        mUpdatesTab.setAlphaLayerValue(MAX_ALPHA - alpha);
     }
 
     @Override
diff --git a/src/com/android/contacts/detail/ContactDetailViewPagerAdapter.java b/src/com/android/contacts/detail/ContactDetailViewPagerAdapter.java
index c708dc8..4213490 100644
--- a/src/com/android/contacts/detail/ContactDetailViewPagerAdapter.java
+++ b/src/com/android/contacts/detail/ContactDetailViewPagerAdapter.java
@@ -29,7 +29,7 @@
 public class ContactDetailViewPagerAdapter extends PagerAdapter {
 
     public static final String ABOUT_FRAGMENT_TAG = "view-pager-about-fragment";
-    public static final String UPDTES_FRAGMENT_TAG = "view-pager-updates-fragment";
+    public static final String UPDATES_FRAGMENT_TAG = "view-pager-updates-fragment";
 
     private static final int INDEX_ABOUT_FRAGMENT = 0;
     private static final int INDEX_UPDATES_FRAGMENT = 1;
diff --git a/src/com/android/contacts/util/ImageViewDrawableSetter.java b/src/com/android/contacts/util/ImageViewDrawableSetter.java
index 412d162..09c6e5c 100644
--- a/src/com/android/contacts/util/ImageViewDrawableSetter.java
+++ b/src/com/android/contacts/util/ImageViewDrawableSetter.java
@@ -61,11 +61,8 @@
         Bitmap bitmap = setCompressedImage(contactData.getPhotoBinaryData());
     }
 
-    public OnClickListener setupContactPhotoForClick(Context context, Result contactData,
-            ImageView photoView, boolean expandPhotoOnClick) {
-        setTarget(photoView);
-        Bitmap bitmap = setCompressedImage(contactData.getPhotoBinaryData());
-        return setupClickListener(context, contactData, bitmap, expandPhotoOnClick);
+    public ImageView getTarget() {
+        return mTarget;
     }
 
     /**
@@ -73,7 +70,7 @@
      * is set, it will immediately be applied to the target (there will be no
      * fade transition).
      */
-    private void setTarget(ImageView target) {
+    protected void setTarget(ImageView target) {
         if (mTarget != target) {
             mTarget = target;
             mCompressed = null;
@@ -81,7 +78,11 @@
         }
     }
 
-    private Bitmap setCompressedImage(byte[] compressed) {
+    protected byte[] getCompressedImage() {
+        return mCompressed;
+    }
+
+    protected Bitmap setCompressedImage(byte[] compressed) {
         if (mPreviousDrawable == null) {
             // If we don't already have a drawable, skip the exit-early test
             // below; otherwise we might not end up setting the default image.
@@ -129,67 +130,6 @@
                 : ((BitmapDrawable) mPreviousDrawable).getBitmap();
     }
 
-    private static final class PhotoClickListener implements OnClickListener {
-
-        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,
-                byte[] photoBytes, boolean expandPhotoOnClick) {
-            mContext = context;
-            mContactData = contactData;
-            mPhotoBitmap = photoBitmap;
-            mPhotoBytes = photoBytes;
-            mExpandPhotoOnClick = expandPhotoOnClick;
-        }
-
-        @Override
-        public void onClick(View v) {
-            // Assemble the intent.
-            EntityDeltaList delta = EntityDeltaList.fromIterator(
-                    mContactData.getEntities().iterator());
-
-            // Find location and bounds of target view, adjusting based on the
-            // assumed local density.
-            final float appScale =
-                    mContext.getResources().getCompatibilityInfo().applicationScale;
-            final int[] pos = new int[2];
-            v.getLocationOnScreen(pos);
-
-            final Rect rect = new Rect();
-            rect.left = (int) (pos[0] * appScale + 0.5f);
-            rect.top = (int) (pos[1] * appScale + 0.5f);
-            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,
-                    photoUri, mPhotoBitmap, mPhotoBytes, rect, delta, mContactData.isUserProfile(),
-                    mContactData.isDirectoryEntry(), mExpandPhotoOnClick);
-            // Cache the bitmap directly, so the activity can pull it from the photo manager.
-            if (mPhotoBitmap != null) {
-                ContactPhotoManager.getInstance(mContext).cacheBitmap(
-                        photoUri, mPhotoBitmap, mPhotoBytes);
-            }
-            mContext.startActivity(photoSelectionIntent);
-        }
-    }
-
-    private OnClickListener setupClickListener(Context context, Result contactData, Bitmap bitmap,
-            boolean expandPhotoOnClick) {
-        if (mTarget == null) return null;
-
-        OnClickListener clickListener = new PhotoClickListener(
-                context, contactData, bitmap, mCompressed, expandPhotoOnClick);
-        mTarget.setOnClickListener(clickListener);
-        return clickListener;
-    }
-
     /**
      * Obtain the default drawable for a contact when no photo is available.
      */
diff --git a/src/com/android/contacts/util/MoreMath.java b/src/com/android/contacts/util/MoreMath.java
new file mode 100644
index 0000000..6f28ccd
--- /dev/null
+++ b/src/com/android/contacts/util/MoreMath.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2012 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.contacts.util;
+
+/**
+ * Useful math functions that aren't in java.lang.Math
+ */
+public class MoreMath {
+    /**
+     * If the input value lies outside of the specified range, return the nearer
+     * bound. Otherwise, return the input value, unchanged.
+     */
+    public static float clamp(float input, float lowerBound, float upperBound) {
+        if (input < lowerBound) return lowerBound;
+        if (input > upperBound) return upperBound;
+        return input;
+    }
+
+    /**
+     * If the input value lies outside of the specified range, return the nearer
+     * bound. Otherwise, return the input value, unchanged.
+     */
+    public static double clamp(double input, double lowerBound, double upperBound) {
+        if (input < lowerBound) return lowerBound;
+        if (input > upperBound) return upperBound;
+        return input;
+    }
+}