Merge "Scrim opacity must satisfy GAR" into oc-dr1-dev
diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java
index 6c1efa4..8b2a2dc 100644
--- a/core/java/com/android/internal/graphics/ColorUtils.java
+++ b/core/java/com/android/internal/graphics/ColorUtils.java
@@ -106,6 +106,31 @@
     }
 
     /**
+     * Calculates the minimum alpha value which can be applied to {@code background} so that would
+     * have a contrast value of at least {@code minContrastRatio} when alpha blended to
+     * {@code foreground}.
+     *
+     * @param foreground       the foreground color
+     * @param background       the background color, opacity will be ignored
+     * @param minContrastRatio the minimum contrast ratio
+     * @return the alpha value in the range 0-255, or -1 if no value could be calculated
+     */
+    public static int calculateMinimumBackgroundAlpha(@ColorInt int foreground,
+            @ColorInt int background, float minContrastRatio) {
+        // Ignore initial alpha that the background might have since this is
+        // what we're trying to calculate.
+        background = setAlphaComponent(background, 255);
+        final int leastContrastyColor = setAlphaComponent(foreground, 255);
+        return binaryAlphaSearch(foreground, background, minContrastRatio, (fg, bg, alpha) -> {
+            int testBackground = blendARGB(leastContrastyColor, bg, alpha/255f);
+            // Float rounding might set this alpha to something other that 255,
+            // raising an exception in calculateContrast.
+            testBackground = setAlphaComponent(testBackground, 255);
+            return calculateContrast(fg, testBackground);
+        });
+    }
+
+    /**
      * Calculates the minimum alpha value which can be applied to {@code foreground} so that would
      * have a contrast value of at least {@code minContrastRatio} when compared to
      * {@code background}.
@@ -122,14 +147,33 @@
                     + Integer.toHexString(background));
         }
 
+        ContrastCalculator contrastCalculator = (fg, bg, alpha) -> {
+            int testForeground = setAlphaComponent(fg, alpha);
+            return calculateContrast(testForeground, bg);
+        };
+
         // First lets check that a fully opaque foreground has sufficient contrast
-        int testForeground = setAlphaComponent(foreground, 255);
-        double testRatio = calculateContrast(testForeground, background);
+        double testRatio = contrastCalculator.calculateContrast(foreground, background, 255);
         if (testRatio < minContrastRatio) {
             // Fully opaque foreground does not have sufficient contrast, return error
             return -1;
         }
+        foreground = setAlphaComponent(foreground, 255);
+        return binaryAlphaSearch(foreground, background, minContrastRatio, contrastCalculator);
+    }
 
+    /**
+     * Calculates the alpha value using binary search based on a given contrast evaluation function
+     * and target contrast that needs to be satisfied.
+     *
+     * @param foreground         the foreground color
+     * @param background         the opaque background color
+     * @param minContrastRatio   the minimum contrast ratio
+     * @param calculator function that calculates contrast
+     * @return the alpha value in the range 0-255, or -1 if no value could be calculated
+     */
+    private static int binaryAlphaSearch(@ColorInt int foreground, @ColorInt int background,
+            float minContrastRatio, ContrastCalculator calculator) {
         // Binary search to find a value with the minimum value which provides sufficient contrast
         int numIterations = 0;
         int minAlpha = 0;
@@ -139,9 +183,8 @@
                 (maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) {
             final int testAlpha = (minAlpha + maxAlpha) / 2;
 
-            testForeground = setAlphaComponent(foreground, testAlpha);
-            testRatio = calculateContrast(testForeground, background);
-
+            final double testRatio = calculator.calculateContrast(foreground, background,
+                    testAlpha);
             if (testRatio < minContrastRatio) {
                 minAlpha = testAlpha;
             } else {
@@ -615,4 +658,8 @@
         return result;
     }
 
+    private interface ContrastCalculator {
+        double calculateContrast(int foreground, int background, int alpha);
+    }
+
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index eabf07b..5b2b50b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -37,6 +37,7 @@
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener;
+import com.android.internal.graphics.ColorUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
@@ -88,6 +89,7 @@
     private boolean mNeedsDrawableColorUpdate;
 
     protected float mScrimBehindAlpha;
+    protected float mScrimBehindAlphaResValue;
     protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
     protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
 
@@ -142,7 +144,10 @@
         mUnlockMethodCache = UnlockMethodCache.getInstance(context);
         mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
         mLightBarController = lightBarController;
-        mScrimBehindAlpha = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
+        mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
+        // Scrim alpha is initially set to the value on the resource but might be changed
+        // to make sure that text on top of it is legible.
+        mScrimBehindAlpha = mScrimBehindAlphaResValue;
 
         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mColorExtractor.addOnColorsChangedListener(this);
@@ -342,20 +347,30 @@
     }
 
     protected void updateScrims() {
-        // Make sure we have the right gradients
+        // Make sure we have the right gradients and their opacities will satisfy GAR.
         if (mNeedsDrawableColorUpdate) {
             mNeedsDrawableColorUpdate = false;
+            final GradientColors currentScrimColors;
             if (mKeyguardShowing) {
                 // Always animate color changes if we're seeing the keyguard
                 mScrimInFront.setColors(mLockColors, true /* animated */);
                 mScrimBehind.setColors(mLockColors, true /* animated */);
+                currentScrimColors = mLockColors;
             } else {
                 // Only animate scrim color if the scrim view is actually visible
                 boolean animateScrimInFront = mScrimInFront.getViewAlpha() != 0;
                 boolean animateScrimBehind = mScrimBehind.getViewAlpha() != 0;
                 mScrimInFront.setColors(mSystemColors, animateScrimInFront);
                 mScrimBehind.setColors(mSystemColors, animateScrimBehind);
+                currentScrimColors = mSystemColors;
             }
+
+            // Calculate minimum scrim opacity for white or black text.
+            int textColor = currentScrimColors.supportsDarkText() ? Color.BLACK : Color.WHITE;
+            int mainColor = currentScrimColors.getMainColor();
+            float minOpacity = ColorUtils.calculateMinimumBackgroundAlpha(textColor, mainColor,
+                    4.5f /* minimumContrast */) / 255f;
+            mScrimBehindAlpha = Math.max(mScrimBehindAlphaResValue, minOpacity);
             mLightBarController.setScrimColor(mScrimInFront.getColors());
         }
 
diff --git a/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
new file mode 100644
index 0000000..73df9a0
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/graphics/ColorUtilsTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2017 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.internal.graphics;
+
+import android.graphics.Color;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+@SmallTest
+public class ColorUtilsTest {
+
+    @Test
+    public void calculateMinimumBackgroundAlpha_satisfiestContrast() {
+
+        int alpha = ColorUtils.calculateMinimumBackgroundAlpha(Color.WHITE, Color.BLACK, 4.5f);
+        assertTrue("Alpha doesn't need to be 255 to satisfy contrast", alpha < 255);
+
+        int worstCase = ColorUtils.blendARGB(Color.WHITE, Color.BLACK, alpha/255f);
+        worstCase = ColorUtils.setAlphaComponent(worstCase, 255);
+        double contrast = ColorUtils.calculateContrast(Color.WHITE, worstCase);
+        assertTrue("Blended color should satisfy contrast", contrast >= 4.5);
+
+    }
+}