Merge "Fix NPE in decodedBitmapDrawable" into lmp-mr1-dev
diff --git a/res/layout/quickcontact_header.xml b/res/layout/quickcontact_header.xml
index 010146b..88a80f7 100644
--- a/res/layout/quickcontact_header.xml
+++ b/res/layout/quickcontact_header.xml
@@ -34,13 +34,11 @@
         android:id="@+id/title_gradient"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:visibility="gone"
         android:layout_gravity="bottom" />
     <View
         android:id="@+id/action_bar_gradient"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:visibility="gone"
         android:layout_gravity="top" />
 
     <!-- Need to set a non null background on Toolbar in order for MenuItem ripples to be drawn on
diff --git a/src/com/android/contacts/activities/ActionBarAdapter.java b/src/com/android/contacts/activities/ActionBarAdapter.java
index 6a5c1cb..634f9ad 100644
--- a/src/com/android/contacts/activities/ActionBarAdapter.java
+++ b/src/com/android/contacts/activities/ActionBarAdapter.java
@@ -143,7 +143,7 @@
                 new OnClickListener() {
             @Override
             public void onClick(View v) {
-                mSearchView.setText(null);
+                setQueryString(null);
             }
         });
         mSearchContainer.findViewById(R.id.search_back_button).setOnClickListener(
@@ -254,9 +254,8 @@
             }
             if (mSearchMode) {
                 setFocusOnSearchView();
-            } else {
-                mSearchView.setText(null);
             }
+            setQueryString(null);
         } else if (flag) {
             // Everything is already set up. Still make sure the keyboard is up
             if (mSearchView != null) setFocusOnSearchView();
@@ -271,6 +270,10 @@
         mQueryString = query;
         if (mSearchView != null) {
             mSearchView.setText(query);
+            // When programmatically entering text into the search view, the most reasonable
+            // place for the cursor is after all the text.
+            mSearchView.setSelection(mSearchView.getText() == null ?
+                    0 : mSearchView.getText().length());
         }
     }
 
diff --git a/src/com/android/contacts/activities/PeopleActivity.java b/src/com/android/contacts/activities/PeopleActivity.java
index ba77438..f7c2de2 100644
--- a/src/com/android/contacts/activities/PeopleActivity.java
+++ b/src/com/android/contacts/activities/PeopleActivity.java
@@ -1188,8 +1188,8 @@
                         && !Character.isWhitespace(unicodeChar)) {
                     String query = new String(new int[]{ unicodeChar }, 0, 1);
                     if (!mActionBarAdapter.isSearchMode()) {
-                        mActionBarAdapter.setQueryString(query);
                         mActionBarAdapter.setSearchMode(true);
+                        mActionBarAdapter.setQueryString(query);
                         return true;
                     }
                 }
diff --git a/src/com/android/contacts/quickcontact/QuickContactActivity.java b/src/com/android/contacts/quickcontact/QuickContactActivity.java
index 70d3281..1da010c 100644
--- a/src/com/android/contacts/quickcontact/QuickContactActivity.java
+++ b/src/com/android/contacts/quickcontact/QuickContactActivity.java
@@ -874,7 +874,6 @@
         mPhotoView.setIsBusiness(mContactData.isDisplayNameFromOrganization());
         mPhotoSetter.setupContactPhoto(data, mPhotoView);
         extractAndApplyTintFromPhotoViewAsynchronously();
-        analyzeWhitenessOfPhotoAsynchronously();
         setHeaderNameText(ContactDisplayUtils.getDisplayName(this, data).toString());
 
         Trace.endSection();
@@ -1824,30 +1823,6 @@
         }.execute();
     }
 
-    /**
-     * Examine how many white pixels are in the bitmap in order to determine whether or not
-     * we need gradient overlays on top of the image.
-     */
-    private void analyzeWhitenessOfPhotoAsynchronously() {
-        final Drawable imageViewDrawable = mPhotoView.getDrawable();
-        new AsyncTask<Void, Void, Boolean>() {
-            @Override
-            protected Boolean doInBackground(Void... params) {
-                if (imageViewDrawable instanceof BitmapDrawable) {
-                    final Bitmap bitmap = ((BitmapDrawable) imageViewDrawable).getBitmap();
-                    return WhitenessUtils.isBitmapWhiteAtTopOrBottom(bitmap);
-                }
-                return !(imageViewDrawable instanceof LetterTileDrawable);
-            }
-
-            @Override
-            protected void onPostExecute(Boolean isWhite) {
-                super.onPostExecute(isWhite);
-                mScroller.setUseGradient(isWhite);
-            }
-        }.execute();
-    }
-
     private void setThemeColor(MaterialPalette palette) {
         // If the color is invalid, use the predefined default
         final int primaryColor = palette.mPrimaryColor;
diff --git a/src/com/android/contacts/quickcontact/WhitenessUtils.java b/src/com/android/contacts/quickcontact/WhitenessUtils.java
deleted file mode 100644
index 0d2f2e2..0000000
--- a/src/com/android/contacts/quickcontact/WhitenessUtils.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2014 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.quickcontact;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-import android.os.Trace;
-
-/**
- * Utility class for determining whether Bitmaps contain a lot of white pixels in locations
- * where QuickContactActivity will want to place white text or buttons.
- *
- * This class liberally considers bitmaps white. All constants are chosen with a small amount of
- * experimentation. Despite a lack of rigour, this class successfully allows QuickContactsActivity
- * to detect when Bitmap are obviously *not* white. Therefore, it is better than nothing.
- */
-public class WhitenessUtils {
-
-    /**
-     * Analyze this amount of the top and bottom of the bitmap.
-     */
-    private static final float HEIGHT_PERCENT_ANALYZED = 0.2f;
-
-    /**
-     * An image with more than this amount white, is considered to be a whitish image.
-     */
-    private static final float PROPORTION_WHITE_CUTOFF = 0.1f;
-
-    private static final float THIRD = 0.33f;
-
-    /**
-     * Colors with luma greater than this are considered close to white. This value is lower than
-     * the value used in Palette's ColorUtils, since we want to liberally declare images white.
-     */
-    private static final float LUMINANCE_OF_WHITE =  0.90f;
-
-    /**
-     * Returns true if 20% of the image's top right corner is white, or 20% of the bottom
-     * of the image is white.
-     */
-    public static boolean isBitmapWhiteAtTopOrBottom(Bitmap largeBitmap) {
-        Trace.beginSection("isBitmapWhiteAtTopOrBottom");
-        try {
-            final Bitmap smallBitmap = scaleBitmapDown(largeBitmap);
-
-            final int[] rgbPixels = new int[smallBitmap.getWidth() * smallBitmap.getHeight()];
-            smallBitmap.getPixels(rgbPixels, 0, smallBitmap.getWidth(), 0, 0,
-                    smallBitmap.getWidth(), smallBitmap.getHeight());
-
-            // look at top right corner of the bitmap
-            int whiteCount = 0;
-            for (int y = 0; y < smallBitmap.getHeight() * HEIGHT_PERCENT_ANALYZED; y++) {
-                for (int x = (int) (smallBitmap.getWidth() * (1 - THIRD));
-                        x < smallBitmap.getWidth(); x++) {
-                    final int rgb = rgbPixels[y * smallBitmap.getWidth() + x];
-                    if (isWhite(rgb)) {
-                        whiteCount ++;
-                    }
-                }
-            }
-            int totalPixels = (int) (smallBitmap.getHeight() * smallBitmap.getWidth()
-                    * THIRD * HEIGHT_PERCENT_ANALYZED);
-            if (whiteCount / (float) totalPixels > PROPORTION_WHITE_CUTOFF) {
-                return true;
-            }
-
-            // look at bottom portion of bitmap
-            whiteCount = 0;
-            for (int y = (int) (smallBitmap.getHeight() * (1 - HEIGHT_PERCENT_ANALYZED));
-                    y <  smallBitmap.getHeight(); y++) {
-                for (int x = 0; x < smallBitmap.getWidth(); x++) {
-                    final int rgb = rgbPixels[y * smallBitmap.getWidth() + x];
-                    if (isWhite(rgb)) {
-                        whiteCount ++;
-                    }
-                }
-            }
-
-            totalPixels = (int) (smallBitmap.getHeight()
-                    * smallBitmap.getWidth() * HEIGHT_PERCENT_ANALYZED);
-
-            return whiteCount / (float) totalPixels > PROPORTION_WHITE_CUTOFF;
-        } finally {
-            Trace.endSection();
-        }
-    }
-
-    private static boolean isWhite(int rgb) {
-        return calculateXyzLuma(rgb) > LUMINANCE_OF_WHITE;
-    }
-
-    private static float calculateXyzLuma(int rgb) {
-        return (0.2126f * Color.red(rgb) +
-                0.7152f * Color.green(rgb) +
-                0.0722f * Color.blue(rgb)) / 255f;
-    }
-
-    /**
-     * Scale down the bitmap in order to make color analysis faster. Taken from Palette.
-     */
-    private static Bitmap scaleBitmapDown(Bitmap bitmap) {
-        final int CALCULATE_BITMAP_MIN_DIMENSION = 100;
-        final int minDimension = Math.min(bitmap.getWidth(), bitmap.getHeight());
-
-        if (minDimension <= CALCULATE_BITMAP_MIN_DIMENSION) {
-            // If the bitmap is small enough already, just return it
-            return bitmap;
-        }
-
-        final float scaleRatio = CALCULATE_BITMAP_MIN_DIMENSION / (float) minDimension;
-        return Bitmap.createScaledBitmap(bitmap,
-                Math.round(bitmap.getWidth() * scaleRatio),
-                Math.round(bitmap.getHeight() * scaleRatio),
-                false);
-    }
-}
diff --git a/src/com/android/contacts/widget/MultiShrinkScroller.java b/src/com/android/contacts/widget/MultiShrinkScroller.java
index 5106978..f7f0c7b 100644
--- a/src/com/android/contacts/widget/MultiShrinkScroller.java
+++ b/src/com/android/contacts/widget/MultiShrinkScroller.java
@@ -76,14 +76,21 @@
     private static final int EXIT_FLING_ANIMATION_DURATION_MS = 300;
 
     /**
-     * Length of the entrance animation.
-     */
-    private static final int ENTRANCE_ANIMATION_SLIDE_OPEN_DURATION_MS = 250;
-
-    /**
      * In portrait mode, the height:width ratio of the photo's starting height.
      */
-    private static final float INTERMEDIATE_HEADER_HEIGHT_RATIO = 0.5f;
+    private static final float INTERMEDIATE_HEADER_HEIGHT_RATIO = 0.6f;
+
+    /**
+     * Color blending will only be performed on the contact photo once the toolbar is compressed
+     * to this ratio of its full height.
+     */
+    private static final float COLOR_BLENDING_START_RATIO = 0.5f;
+
+    /**
+     * When displaying a letter tile drawable, this alpha value should be used at the intermediate
+     * toolbar height.
+     */
+    private static final float DESIRED_INTERMEDIATE_LETTER_TILE_ALPHA = 0.8f;
 
     /**
      * Maximum velocity for flings in dips per second. Picked via non-rigorous experimentation.
@@ -166,14 +173,8 @@
 
     private final PathInterpolator mTextSizePathInterpolator
             = new PathInterpolator(0.16f, 0.4f, 0.2f, 1);
-    /**
-     * Interpolator that starts and ends with nearly straight segments. At x=0 it has a y of
-     * approximately 0.25. We only want the contact photo 25% faded when half collapsed.
-     */
-    private final PathInterpolator mWhiteBlendingPathInterpolator
-            = new PathInterpolator(1.0f, 0.4f, 0.9f, 0.8f);
 
-    private final int[] mGradientColors = new int[] {0,0xAA000000};
+    private final int[] mGradientColors = new int[] {0,0x88000000};
     private GradientDrawable mTitleGradientDrawable = new GradientDrawable(
             GradientDrawable.Orientation.TOP_BOTTOM, mGradientColors);
     private GradientDrawable mActionBarGradientDrawable = new GradientDrawable(
@@ -363,18 +364,17 @@
     }
 
     private void configureGradientViewHeights() {
-        final float GRADIENT_SIZE_COEFFICIENT = 1.25f;
         final FrameLayout.LayoutParams actionBarGradientLayoutParams
                 = (FrameLayout.LayoutParams) mActionBarGradientView.getLayoutParams();
-        actionBarGradientLayoutParams.height
-                = (int) (mActionBarSize * GRADIENT_SIZE_COEFFICIENT);
+        actionBarGradientLayoutParams.height = mActionBarSize;
         mActionBarGradientView.setLayoutParams(actionBarGradientLayoutParams);
         final FrameLayout.LayoutParams titleGradientLayoutParams
                 = (FrameLayout.LayoutParams) mTitleGradientView.getLayoutParams();
+        final float TITLE_GRADIENT_SIZE_COEFFICIENT = 1.25f;
         final FrameLayout.LayoutParams largeTextLayoutParms
                 = (FrameLayout.LayoutParams) mLargeTextView.getLayoutParams();
         titleGradientLayoutParams.height = (int) ((mLargeTextView.getHeight()
-                + largeTextLayoutParms.bottomMargin) * GRADIENT_SIZE_COEFFICIENT);
+                + largeTextLayoutParms.bottomMargin) * TITLE_GRADIENT_SIZE_COEFFICIENT);
         mTitleGradientView.setLayoutParams(titleGradientLayoutParams);
     }
 
@@ -383,13 +383,6 @@
         mPhotoTouchInterceptOverlay.setContentDescription(title);
     }
 
-    public void setUseGradient(boolean useGradient) {
-        if (mTitleGradientView != null) {
-            mTitleGradientView.setVisibility(useGradient ? View.VISIBLE : View.GONE);
-            mActionBarGradientView.setVisibility(useGradient ? View.VISIBLE : View.GONE);
-        }
-    }
-
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
         if (mVelocityTracker == null) {
@@ -1033,9 +1026,7 @@
     }
 
     private void updatePhotoTintAndDropShadow() {
-        // Let's keep an eye on how long this method takes to complete. Right now, it takes ~0.2ms
-        // on a Nexus 5. If it starts to get much slower, there are a number of easy optimizations
-        // available.
+        // Let's keep an eye on how long this method takes to complete.
         Trace.beginSection("updatePhotoTintAndDropShadow");
 
         if (mIsTwoPanel && !mPhotoView.isBasedOffLetterTile()) {
@@ -1058,68 +1049,75 @@
 
         // Reuse an existing mColorFilter (to avoid GC pauses) to change the photo's tint.
         mPhotoView.clearColorFilter();
-
-        // Ratio of current size to maximum size of the header.
-        final float ratio;
-        // The value that "ratio" will have when the header is at its starting/intermediate size.
-        final float intermediateRatio = calculateHeightRatio((int)
-                (mMaximumPortraitHeaderHeight * INTERMEDIATE_HEADER_HEIGHT_RATIO));
-        if (!mIsTwoPanel) {
-            ratio = calculateHeightRatio(toolbarHeight);
-        } else {
-            // We want the ratio and intermediateRatio to have the *approximate* values
-            // they would have in portrait mode when at the intermediate position.
-            ratio = intermediateRatio;
-        }
-
-        final float linearBeforeMiddle = Math.max(1 - (1 - ratio) / intermediateRatio, 0);
-
-        // Want a function with a derivative of 0 at x=0. I don't want it to grow too
-        // slowly before x=0.5. x^1.1 satisfies both requirements.
-        final float EXPONENT_ALMOST_ONE = 1.1f;
-        final float semiLinearBeforeMiddle = (float) Math.pow(linearBeforeMiddle,
-                EXPONENT_ALMOST_ONE);
         mColorMatrix.reset();
-        mColorMatrix.setSaturation(semiLinearBeforeMiddle);
-        mColorMatrix.postConcat(alphaMatrix(
-                1 - mWhiteBlendingPathInterpolator.getInterpolation(1 - ratio), Color.WHITE));
 
-        final float colorAlpha;
-        if (mPhotoView.isBasedOffLetterTile()) {
-            // Since the letter tile only has white and grey, tint it more slowly. Otherwise
-            // it will be completely invisible before we reach the intermediate point. The values
-            // for TILE_EXPONENT and slowingFactor are chosen to achieve DESIRED_INTERMEDIATE_ALPHA
-            // at the intermediate/starting position.
-            final float DESIRED_INTERMEDIATE_ALPHA = 0.9f;
-            final float TILE_EXPONENT = 1.5f;
-            final float slowingFactor = (float) ((1 - intermediateRatio) / intermediateRatio
-                    / (1 - Math.pow(1 - DESIRED_INTERMEDIATE_ALPHA, 1/TILE_EXPONENT)));
-            float linearBeforeMiddleish = Math.max(1 - (1 - ratio) / intermediateRatio
-                    / slowingFactor, 0);
-            colorAlpha = 1 - (float) Math.pow(linearBeforeMiddleish, TILE_EXPONENT);
-            mColorMatrix.postConcat(alphaMatrix(colorAlpha, mHeaderTintColor));
+        final int gradientAlpha;
+        if (!mPhotoView.isBasedOffLetterTile()) {
+            // Constants and equations were arbitrarily picked to choose values for saturation,
+            // whiteness, tint and gradient alpha. There were four main objectives:
+            // 1) The transition period between the unmodified image and fully colored image should
+            //    be very short.
+            // 2) The tinting should be fully applied even before the background image is fully
+            //    faded out and desaturated. Why? A half tinted photo looks bad and results in
+            //    unappealing colors.
+            // 3) The function should have a derivative of 0 at ratio = 1 to avoid discontinuities.
+            // 4) The entire process should look awesome.
+            final float ratio = calculateHeightRatioToBlendingStartHeight(toolbarHeight);
+            final float alpha = 1.0f - (float) Math.min(Math.pow(ratio, 1.5f) * 2f, 1f);
+            final float tint = (float) Math.min(Math.pow(ratio, 1.5f) * 3f, 1f);
+            mColorMatrix.setSaturation(alpha);
+            mColorMatrix.postConcat(alphaMatrix(alpha, Color.WHITE));
+            mColorMatrix.postConcat(multiplyBlendMatrix(mHeaderTintColor, tint));
+            gradientAlpha = (int) (255 * alpha);
+        } else if (mIsTwoPanel) {
+            mColorMatrix.reset();
+            mColorMatrix.postConcat(alphaMatrix(DESIRED_INTERMEDIATE_LETTER_TILE_ALPHA,
+                    mHeaderTintColor));
+            gradientAlpha = 0;
         } else {
-            colorAlpha = 1 - semiLinearBeforeMiddle;
-            mColorMatrix.postConcat(multiplyBlendMatrix(mHeaderTintColor, colorAlpha));
+            // We want a function that has DESIRED_INTERMEDIATE_LETTER_TILE_ALPHA value
+            // at the intermediate position and uses TILE_EXPONENT. Finding an equation
+            // that satisfies this condition requires the following arithmetic.
+            final float ratio = calculateHeightRatioToFullyOpen(toolbarHeight);
+            final float intermediateRatio = calculateHeightRatioToFullyOpen((int)
+                    (mMaximumPortraitHeaderHeight * INTERMEDIATE_HEADER_HEIGHT_RATIO));
+            final float TILE_EXPONENT = 3f;
+            final float slowingFactor = (float) ((1 - intermediateRatio) / intermediateRatio
+                    / (1 - Math.pow(1 - DESIRED_INTERMEDIATE_LETTER_TILE_ALPHA, 1/TILE_EXPONENT)));
+            float linearBeforeIntermediate = Math.max(1 - (1 - ratio) / intermediateRatio
+                    / slowingFactor, 0);
+            float colorAlpha = 1 - (float) Math.pow(linearBeforeIntermediate, TILE_EXPONENT);
+            mColorMatrix.postConcat(alphaMatrix(colorAlpha, mHeaderTintColor));
+            gradientAlpha = 0;
         }
 
+        // TODO: remove re-allocation of ColorMatrixColorFilter objects (b/17627000)
         mPhotoView.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
+
         // Tell the photo view what tint we are trying to achieve. Depending on the type of
         // drawable used, the photo view may or may not use this tint.
         mPhotoView.setTint(mHeaderTintColor);
-
-        final int gradientAlpha = (int) (255 * linearBeforeMiddle);
         mTitleGradientDrawable.setAlpha(gradientAlpha);
         mActionBarGradientDrawable.setAlpha(gradientAlpha);
 
         Trace.endSection();
     }
 
-    private float calculateHeightRatio(int height) {
+    private float calculateHeightRatioToFullyOpen(int height) {
         return (height - mMinimumPortraitHeaderHeight)
                 / (float) (mMaximumPortraitHeaderHeight - mMinimumPortraitHeaderHeight);
     }
 
+    private float calculateHeightRatioToBlendingStartHeight(int height) {
+        final float intermediateHeight = mMaximumPortraitHeaderHeight
+                * COLOR_BLENDING_START_RATIO;
+        final float interpolatingHeightRange = intermediateHeight - mMinimumPortraitHeaderHeight;
+        if (height > intermediateHeight) {
+            return 0;
+        }
+        return (intermediateHeight - height) / interpolatingHeightRange;
+    }
+
     /**
      * Simulates alpha blending an image with {@param color}.
      */