Merge "Fix subsequent drag-to-open SubMenus" into lmp-mr1-ub-dev
diff --git a/build.gradle b/build.gradle
index 82de0c2..5d8a0cb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@
         maven { url '../../prebuilts/tools/common/m2/internal' }
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:1.2.3'
+        classpath 'com.android.tools.build:gradle:1.3.0-beta2'
     }
 }
 
@@ -166,6 +166,7 @@
     project.plugins.whenPluginAdded { plugin ->
         if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)) {
             project.android.buildToolsVersion = rootProject.buildToolsVersion
+            project.android.aaptOptions.useNewCruncher = false
         }
     }
 }
diff --git a/design/api/current.txt b/design/api/current.txt
index 9dc6e20..e630e17 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -229,6 +229,7 @@
     method public android.support.design.widget.Snackbar setText(java.lang.CharSequence);
     method public android.support.design.widget.Snackbar setText(int);
     method public void show();
+    field public static final int LENGTH_INDEFINITE = -2; // 0xfffffffe
     field public static final int LENGTH_LONG = 0; // 0x0
     field public static final int LENGTH_SHORT = -1; // 0xffffffff
   }
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 7c09925..71611e6 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -377,14 +377,22 @@
 
     private void showScrim() {
         if (!mScrimsAreShown) {
-            animateScrim(255);
+            if (ViewCompat.isLaidOut(this) && !isInEditMode()) {
+                animateScrim(255);
+            } else {
+                setScrimAlpha(255);
+            }
             mScrimsAreShown = true;
         }
     }
 
     private void hideScrim() {
         if (mScrimsAreShown) {
-            animateScrim(0);
+            if (ViewCompat.isLaidOut(this) && !isInEditMode()) {
+                animateScrim(0);
+            } else {
+                setScrimAlpha(0);
+            }
             mScrimsAreShown = false;
         }
     }
@@ -398,15 +406,7 @@
             mScrimAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
                 @Override
                 public void onAnimationUpdate(ValueAnimatorCompat animator) {
-                    final int newAlpha = animator.getAnimatedIntValue();
-                    if (newAlpha != mScrimAlpha) {
-                        final Drawable contentScrim = mContentScrim;
-                        if (contentScrim != null && mToolbar != null) {
-                            ViewCompat.postInvalidateOnAnimation(mToolbar);
-                        }
-                        mScrimAlpha = newAlpha;
-                        ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
-                    }
+                    setScrimAlpha(animator.getAnimatedIntValue());
                 }
             });
         } else if (mScrimAnimator.isRunning()) {
@@ -417,6 +417,17 @@
         mScrimAnimator.start();
     }
 
+    private void setScrimAlpha(int alpha) {
+        if (alpha != mScrimAlpha) {
+            final Drawable contentScrim = mContentScrim;
+            if (contentScrim != null && mToolbar != null) {
+                ViewCompat.postInvalidateOnAnimation(mToolbar);
+            }
+            mScrimAlpha = alpha;
+            ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
+        }
+    }
+
     /**
      * Set the drawable to use for the content scrim from resources. Providing null will disable
      * the scrim functionality.
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index 13262a2..42af049 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -247,7 +247,7 @@
         if (getVisibility() != VISIBLE) {
             return;
         }
-        if (ViewCompat.isLaidOut(this)) {
+        if (ViewCompat.isLaidOut(this) && !isInEditMode()) {
             mImpl.hide();
         } else {
             setVisibility(GONE);
diff --git a/design/src/android/support/design/widget/Snackbar.java b/design/src/android/support/design/widget/Snackbar.java
index f3df4f9..83888ce 100644
--- a/design/src/android/support/design/widget/Snackbar.java
+++ b/design/src/android/support/design/widget/Snackbar.java
@@ -95,11 +95,19 @@
     /**
      * @hide
      */
-    @IntDef({LENGTH_SHORT, LENGTH_LONG})
+    @IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Duration {}
 
     /**
+     * Show the Snackbar indefinitely. This means that the Snackbar will be displayed from the time
+     * that is {@link #show() shown} until either it is dismissed, or another Snackbar is shown.
+     *
+     * @see #setDuration
+     */
+    public static final int LENGTH_INDEFINITE = -2;
+
+    /**
      * Show the Snackbar for a short period of time.
      *
      * @see #setDuration
diff --git a/design/src/android/support/design/widget/SnackbarManager.java b/design/src/android/support/design/widget/SnackbarManager.java
index c6b8f18..c4c54a2 100644
--- a/design/src/android/support/design/widget/SnackbarManager.java
+++ b/design/src/android/support/design/widget/SnackbarManager.java
@@ -200,6 +200,11 @@
     }
 
     private void scheduleTimeoutLocked(SnackbarRecord r) {
+        if (r.duration == Snackbar.LENGTH_INDEFINITE) {
+            // If we're set to indefinite, we don't want to set a timeout
+            return;
+        }
+
         int durationMs = LONG_DURATION_MS;
         if (r.duration > 0) {
             durationMs = r.duration;
diff --git a/design/src/android/support/design/widget/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
index 6e0153a..a51170f 100755
--- a/design/src/android/support/design/widget/TabLayout.java
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -785,6 +785,10 @@
     }
 
     void selectTab(Tab tab) {
+        selectTab(tab, true);
+    }
+
+    void selectTab(Tab tab, boolean updateIndicator) {
         if (mSelectedTab == tab) {
             if (mSelectedTab != null) {
                 if (mOnTabSelectedListener != null) {
@@ -795,15 +799,15 @@
         } else {
             final int newPosition = tab != null ? tab.getPosition() : Tab.INVALID_POSITION;
             setSelectedTabView(newPosition);
-
-            if ((mSelectedTab == null || mSelectedTab.getPosition() == Tab.INVALID_POSITION)
-                    && newPosition != Tab.INVALID_POSITION) {
-                // If we don't currently have a tab, just draw the indicator
-                setScrollPosition(newPosition, 0f, true);
-            } else {
-                animateToTab(newPosition);
+            if (updateIndicator) {
+                if ((mSelectedTab == null || mSelectedTab.getPosition() == Tab.INVALID_POSITION)
+                        && newPosition != Tab.INVALID_POSITION) {
+                    // If we don't currently have a tab, just draw the indicator
+                    setScrollPosition(newPosition, 0f, true);
+                } else {
+                    animateToTab(newPosition);
+                }
             }
-
             if (mSelectedTab != null && mOnTabSelectedListener != null) {
                 mOnTabSelectedListener.onTabUnselected(mSelectedTab);
             }
@@ -1146,14 +1150,13 @@
         public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 
-            if (mTabMaxWidth != 0 && getMeasuredWidth() > mTabMaxWidth) {
-                // Re-measure if we went beyond our maximum size.
-                super.onMeasure(MeasureSpec.makeMeasureSpec(
-                        mTabMaxWidth, MeasureSpec.EXACTLY), heightMeasureSpec);
-            } else if (mTabMinWidth > 0 && getMeasuredHeight() < mTabMinWidth) {
-                // Re-measure if we're below our minimum size.
-                super.onMeasure(MeasureSpec.makeMeasureSpec(
-                        mTabMinWidth, MeasureSpec.EXACTLY), heightMeasureSpec);
+            final int measuredWidth = getMeasuredWidth();
+            if (measuredWidth < mTabMinWidth || measuredWidth > mTabMaxWidth) {
+                // Re-measure if we are outside our min or max width
+                widthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                        MathUtils.constrain(measuredWidth, mTabMinWidth, mTabMaxWidth),
+                        MeasureSpec.EXACTLY);
+                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
             }
         }
 
@@ -1556,8 +1559,11 @@
         @Override
         public void onPageSelected(int position) {
             final TabLayout tabLayout = mTabLayoutRef.get();
-            if (mScrollState == SCROLL_STATE_IDLE && tabLayout != null) {
-                tabLayout.getTabAt(position).select();
+            if (tabLayout != null) {
+                // Select the tab, only updating the indicator if we're not being dragged/settled
+                // (since onPageScrolled will handle that).
+                tabLayout.selectTab(tabLayout.getTabAt(position),
+                        mScrollState == SCROLL_STATE_IDLE);
             }
         }
     }
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index 884e538..15173cc 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -17,7 +17,6 @@
 package android.support.design.widget;
 
 import android.content.Context;
-import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -81,7 +80,8 @@
     private TextView mErrorView;
     private int mErrorTextAppearance;
 
-    private ColorStateList mLabelTextColor;
+    private int mDefaultTextColor;
+    private int mFocusedTextColor;
 
     private final CollapsingTextHelper mCollapsingTextHelper;
     private final Handler mHandler;
@@ -127,14 +127,12 @@
 
         mErrorTextAppearance = a.getResourceId(R.styleable.TextInputLayout_errorTextAppearance, 0);
         final boolean errorEnabled = a.getBoolean(R.styleable.TextInputLayout_errorEnabled, false);
+        
+        mDefaultTextColor = getThemeAttrColor(android.R.attr.textColorHint);
+        mFocusedTextColor = mCollapsingTextHelper.getCollapsedTextColor();
 
-        // We create a ColorStateList using the specified text color, combining it with our
-        // theme's textColorHint
-        mLabelTextColor = createLabelTextColorStateList(
-                mCollapsingTextHelper.getCollapsedTextColor());
-
-        mCollapsingTextHelper.setCollapsedTextColor(mLabelTextColor.getDefaultColor());
-        mCollapsingTextHelper.setExpandedTextColor(mLabelTextColor.getDefaultColor());
+        mCollapsingTextHelper.setCollapsedTextColor(mDefaultTextColor);
+        mCollapsingTextHelper.setExpandedTextColor(mDefaultTextColor);
 
         a.recycle();
 
@@ -199,6 +197,9 @@
             }
         });
 
+        // Use the EditText's hint colors since the developer may have changed it
+        mDefaultTextColor = mEditText.getHintTextColors().getDefaultColor();
+
         // Add focus listener to the EditText so that we can notify the label that it is activated.
         // Allows the use of a ColorStateList for the text color on the label
         mEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
@@ -239,9 +240,9 @@
         boolean hasText = !TextUtils.isEmpty(mEditText.getText());
         boolean isFocused = mEditText.isFocused();
 
-        mCollapsingTextHelper.setCollapsedTextColor(mLabelTextColor.getColorForState(
-                isFocused ? FOCUSED_STATE_SET : EMPTY_STATE_SET,
-                mLabelTextColor.getDefaultColor()));
+        mCollapsingTextHelper.setExpandedTextColor(mDefaultTextColor);
+        mCollapsingTextHelper.setCollapsedTextColor(
+                isFocused ? mFocusedTextColor : mDefaultTextColor);
 
         if (hasText || isFocused) {
             // We should be showing the label so do so if it isn't already
@@ -404,23 +405,6 @@
         mAnimator.start();
     }
 
-    private ColorStateList createLabelTextColorStateList(int color) {
-        final int[][] states = new int[2][];
-        final int[] colors = new int[2];
-        int i = 0;
-
-        // Focused
-        states[i] = FOCUSED_STATE_SET;
-        colors[i] = color;
-        i++;
-
-        states[i] = EMPTY_STATE_SET;
-        colors[i] = getThemeAttrColor(android.R.attr.textColorHint);
-        i++;
-
-        return new ColorStateList(states, colors);
-    }
-
     private int getThemeAttrColor(int attr) {
         TypedValue tv = new TypedValue();
         if (getContext().getTheme().resolveAttribute(attr, tv, true)) {
diff --git a/v7/appcompat/res/values-v21/styles_base.xml b/v7/appcompat/res/values-v21/styles_base.xml
index 1bf90ef..241cb04 100644
--- a/v7/appcompat/res/values-v21/styles_base.xml
+++ b/v7/appcompat/res/values-v21/styles_base.xml
@@ -174,6 +174,8 @@
 
     <style name="Base.Widget.AppCompat.AutoCompleteTextView" parent="android:Widget.Material.AutoCompleteTextView" />
 
+    <style name="Base.Widget.AppCompat.EditText" parent="android:Widget.Material.EditText" />
+
     <style name="Base.Widget.AppCompat.RatingBar" parent="android:Widget.Material.RatingBar" />
 
     <style name="Base.Widget.AppCompat.Button" parent="android:Widget.Material.Button" />
diff --git a/v7/appcompat/res/values/styles.xml b/v7/appcompat/res/values/styles.xml
index b38c6ad..0ac20d5 100644
--- a/v7/appcompat/res/values/styles.xml
+++ b/v7/appcompat/res/values/styles.xml
@@ -194,9 +194,7 @@
     <style name="Widget.AppCompat.SearchView" parent="Base.Widget.AppCompat.SearchView" />
     <style name="Widget.AppCompat.SearchView.ActionBar" parent="Base.Widget.AppCompat.SearchView.ActionBar" />
 
-    <style name="Widget.AppCompat.EditText"
-           parent="Base.Widget.AppCompat.EditText">
-    </style>
+    <style name="Widget.AppCompat.EditText" parent="Base.Widget.AppCompat.EditText"/>
 
     <style name="Widget.AppCompat.CompoundButton.Switch" parent="Base.Widget.AppCompat.CompoundButton.Switch" />
 
diff --git a/v7/palette/api/current.txt b/v7/palette/api/current.txt
index d92c0dd..1b6c745 100644
--- a/v7/palette/api/current.txt
+++ b/v7/palette/api/current.txt
@@ -25,12 +25,18 @@
   public static final class Palette.Builder {
     ctor public Palette.Builder(android.graphics.Bitmap);
     ctor public Palette.Builder(java.util.List<android.support.v7.graphics.Palette.Swatch>);
+    method public android.support.v7.graphics.Palette.Builder addFilter(android.support.v7.graphics.Palette.Filter);
+    method public android.support.v7.graphics.Palette.Builder clearFilters();
     method public android.support.v7.graphics.Palette generate();
     method public android.os.AsyncTask<android.graphics.Bitmap, java.lang.Void, android.support.v7.graphics.Palette> generate(android.support.v7.graphics.Palette.PaletteAsyncListener);
     method public android.support.v7.graphics.Palette.Builder maximumColorCount(int);
     method public android.support.v7.graphics.Palette.Builder resizeBitmapSize(int);
   }
 
+  public static abstract interface Palette.Filter {
+    method public abstract boolean isAllowed(int, float[]);
+  }
+
   public static abstract interface Palette.PaletteAsyncListener {
     method public abstract void onGenerated(android.support.v7.graphics.Palette);
   }
diff --git a/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java b/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java
index 9355502..3b89748 100644
--- a/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java
+++ b/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java
@@ -16,7 +16,6 @@
 
 package android.support.v7.graphics;
 
-import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.support.v4.graphics.ColorUtils;
 import android.support.v7.graphics.Palette.Swatch;
@@ -47,9 +46,6 @@
     private static final String LOG_TAG = "ColorCutQuantizer";
     private static final boolean LOG_TIMINGS = false;
 
-    private static final float BLACK_MAX_LIGHTNESS = 0.05f;
-    private static final float WHITE_MIN_LIGHTNESS = 0.95f;
-
     private static final int COMPONENT_RED = -3;
     private static final int COMPONENT_GREEN = -2;
     private static final int COMPONENT_BLUE = -1;
@@ -61,33 +57,20 @@
     final int[] mHistogram;
     final List<Swatch> mQuantizedColors;
     final TimingLogger mTimingLogger;
+    final Palette.Filter[] mFilters;
 
     private final float[] mTempHsl = new float[3];
 
     /**
-     * Factory-method to generate a {@link ColorCutQuantizer} from a {@link Bitmap} object.
-     *
-     * @param bitmap Bitmap to extract the pixel data from
-     * @param maxColors The maximum number of colors that should be in the result palette.
-     */
-    static ColorCutQuantizer fromBitmap(Bitmap bitmap, int maxColors) {
-        final int width = bitmap.getWidth();
-        final int height = bitmap.getHeight();
-
-        final int[] pixels = new int[width * height];
-        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
-
-        return new ColorCutQuantizer(pixels, maxColors);
-    }
-
-    /**
-     * Private constructor.
+     * Constructor.
      *
      * @param pixels histogram representing an image's pixel data
      * @param maxColors The maximum number of colors that should be in the result palette.
+     * @param filters Set of filters to use in the quantization stage
      */
-    private ColorCutQuantizer(final int[] pixels, final int maxColors) {
+    ColorCutQuantizer(final int[] pixels, final int maxColors, final Palette.Filter[] filters) {
         mTimingLogger = LOG_TIMINGS ? new TimingLogger(LOG_TAG, "Creation") : null;
+        mFilters = filters;
 
         final int[] hist = mHistogram = new int[1 << (QUANTIZE_WORD_WIDTH * 3)];
         for (int i = 0; i < pixels.length; i++) {
@@ -443,37 +426,24 @@
     }
 
     private boolean shouldIgnoreColor(int color565) {
-        ColorUtils.colorToHSL(approximateToRgb888(color565), mTempHsl);
-        return shouldIgnoreColor(mTempHsl);
+        final int rgb = approximateToRgb888(color565);
+        ColorUtils.colorToHSL(rgb, mTempHsl);
+        return shouldIgnoreColor(rgb, mTempHsl);
     }
 
-    private static boolean shouldIgnoreColor(Swatch color) {
-        return shouldIgnoreColor(color.getHsl());
+    private boolean shouldIgnoreColor(Swatch color) {
+        return shouldIgnoreColor(color.getRgb(), color.getHsl());
     }
 
-    private static boolean shouldIgnoreColor(float[] hslColor) {
-        return isWhite(hslColor) || isBlack(hslColor) || isNearRedILine(hslColor);
-    }
-
-    /**
-     * @return true if the color represents a color which is close to black.
-     */
-    private static boolean isBlack(float[] hslColor) {
-        return hslColor[2] <= BLACK_MAX_LIGHTNESS;
-    }
-
-    /**
-     * @return true if the color represents a color which is close to white.
-     */
-    private static boolean isWhite(float[] hslColor) {
-        return hslColor[2] >= WHITE_MIN_LIGHTNESS;
-    }
-
-    /**
-     * @return true if the color lies close to the red side of the I line.
-     */
-    private static boolean isNearRedILine(float[] hslColor) {
-        return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f;
+    private boolean shouldIgnoreColor(int rgb, float[] hsl) {
+        if (mFilters != null && mFilters.length > 0) {
+            for (int i = 0, count = mFilters.length; i < count; i++) {
+                if (!mFilters[i].isAllowed(rgb, hsl)) {
+                    return true;
+                }
+            }
+        }
+        return false;
     }
 
     /**
diff --git a/v7/palette/src/main/java/android/support/v7/graphics/Palette.java b/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
index 7a4e7ee..c3f3e78 100644
--- a/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
+++ b/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
@@ -23,6 +23,7 @@
 import android.support.v4.os.AsyncTaskCompat;
 import android.util.TimingLogger;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -427,6 +428,7 @@
         private Bitmap mBitmap;
         private int mMaxColors = DEFAULT_CALCULATE_NUMBER_COLORS;
         private int mResizeMaxDimension = DEFAULT_RESIZE_BITMAP_MAX_DIMENSION;
+        private final List<Filter> mFilters = new ArrayList<>();
 
         private Generator mGenerator;
 
@@ -434,6 +436,7 @@
          * Construct a new {@link Builder} using a source {@link Bitmap}
          */
         public Builder(Bitmap bitmap) {
+            this();
             if (bitmap == null || bitmap.isRecycled()) {
                 throw new IllegalArgumentException("Bitmap is not valid");
             }
@@ -445,12 +448,17 @@
          * Typically only used for testing.
          */
         public Builder(List<Swatch> swatches) {
+            this();
             if (swatches == null || swatches.isEmpty()) {
                 throw new IllegalArgumentException("List of Swatches is not valid");
             }
             mSwatches = swatches;
         }
 
+        private Builder() {
+            mFilters.add(DEFAULT_FILTER);
+        }
+
         /**
          * Set the {@link Generator} to use when generating the {@link Palette}. If this is called
          * with {@code null} then the default generator will be used.
@@ -489,6 +497,28 @@
         }
 
         /**
+         * Clear all added filters. This includes any default filters added automatically by
+         * {@link Palette}.
+         */
+        public Builder clearFilters() {
+            mFilters.clear();
+            return this;
+        }
+
+        /**
+         * Add a filter to be able to have fine grained controlled over the colors which are
+         * allowed in the resulting palette.
+         *
+         * @param filter filter to add.
+         */
+        public Builder addFilter(Filter filter) {
+            if (filter != null) {
+                mFilters.add(filter);
+            }
+            return this;
+        }
+
+        /**
          * Generate and return the {@link Palette} synchronously.
          */
         public Palette generate() {
@@ -514,8 +544,13 @@
                 }
 
                 // Now generate a quantizer from the Bitmap
-                ColorCutQuantizer quantizer = ColorCutQuantizer
-                        .fromBitmap(scaledBitmap, mMaxColors);
+                final int width = scaledBitmap.getWidth();
+                final int height = scaledBitmap.getHeight();
+                final int[] pixels = new int[width * height];
+                scaledBitmap.getPixels(pixels, 0, width, 0, 0, width, height);
+
+                final ColorCutQuantizer quantizer = new ColorCutQuantizer(pixels, mMaxColors,
+                        mFilters.isEmpty() ? null : mFilters.toArray(new Filter[mFilters.size()]));
 
                 // If created a new bitmap, recycle it
                 if (scaledBitmap != mBitmap) {
@@ -633,4 +668,55 @@
         }
     }
 
+    /**
+     * A Filter provides a mechanism for exercising fine-grained control over which colors
+     * are valid within a resulting {@link Palette}.
+     */
+    public interface Filter {
+        /**
+         * Hook to allow clients to be able filter colors from resulting palette.
+         *
+         * @param rgb the color in RGB888.
+         * @param hsl HSL representation of the color.
+         *
+         * @return true if the color is allowed, false if not.
+         *
+         * @see Builder#addFilter(Filter)
+         */
+        boolean isAllowed(int rgb, float[] hsl);
+    }
+
+    /**
+     * The default filter.
+     */
+    private static final Filter DEFAULT_FILTER = new Filter() {
+        private static final float BLACK_MAX_LIGHTNESS = 0.05f;
+        private static final float WHITE_MIN_LIGHTNESS = 0.95f;
+
+        @Override
+        public boolean isAllowed(int rgb, float[] hsl) {
+            return !isWhite(hsl) && !isBlack(hsl) && !isNearRedILine(hsl);
+        }
+
+        /**
+         * @return true if the color represents a color which is close to black.
+         */
+        private boolean isBlack(float[] hslColor) {
+            return hslColor[2] <= BLACK_MAX_LIGHTNESS;
+        }
+
+        /**
+         * @return true if the color represents a color which is close to white.
+         */
+        private boolean isWhite(float[] hslColor) {
+            return hslColor[2] >= WHITE_MIN_LIGHTNESS;
+        }
+
+        /**
+         * @return true if the color lies close to the red side of the I line.
+         */
+        private boolean isNearRedILine(float[] hslColor) {
+            return hslColor[0] >= 10f && hslColor[0] <= 37f && hslColor[1] <= 0.82f;
+        }
+    };
 }