From c3eebe9f664af4b77e5948a14bf266b25dc25cc8 Mon Sep 17 00:00:00 2001 From: Maurice Lam Date: Fri, 13 Nov 2015 18:19:09 -0800 Subject: [PATCH] [SuwLib] Implement pattern drawable background - Implement the pattern background for GLIF using a custom drawable. The drawable draws the paths manually as specified in the SVG, for backwards compatibility. - Use a FrameLayout subclass to draw the custom status bar background in the case of phone layouts, and set draw the background directly for tablet / clamshell layouts. Bug: 25650709 Change-Id: I527bb47efa143ec43c1030b57087fd2414d7045b --- library/eclair-mr1/res/values/styles.xml | 2 +- .../suw_glif_recycler_template_card.xml | 4 +- .../suw_glif_recycler_template_compact.xml | 26 +++ library/full-support/res/values/layouts.xml | 4 +- .../layout/suw_glif_list_template_card.xml | 4 +- .../layout/suw_glif_list_template_compact.xml | 26 +++ .../res/layout/suw_glif_template_card.xml | 4 +- .../res/layout/suw_glif_template_compact.xml | 26 +++ library/main/res/values/attrs.xml | 4 + library/main/res/values/layouts.xml | 4 +- .../android/setupwizardlib/GlifLayout.java | 16 ++ .../setupwizardlib/GlifPatternDrawable.java | 198 ++++++++++++++++++ .../view/StatusBarBackgroundLayout.java | 103 +++++++++ .../test/GlifPatternDrawableTest.java | 72 +++++++ .../test/StatusBarBackgroundLayoutTest.java | 35 ++++ 15 files changed, 520 insertions(+), 8 deletions(-) create mode 100644 library/full-support/res/layout/suw_glif_recycler_template_compact.xml create mode 100644 library/main/res/layout/suw_glif_list_template_compact.xml create mode 100644 library/main/res/layout/suw_glif_template_compact.xml create mode 100644 library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java create mode 100644 library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java create mode 100644 library/test/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java create mode 100644 library/test/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java diff --git a/library/eclair-mr1/res/values/styles.xml b/library/eclair-mr1/res/values/styles.xml index f0cce3f..8e4acd4 100644 --- a/library/eclair-mr1/res/values/styles.xml +++ b/library/eclair-mr1/res/values/styles.xml @@ -67,7 +67,7 @@ src_in @dimen/suw_items_preferred_height @android:color/black - ?attr/colorPrimary + @android:color/transparent @color/suw_link_color_light @style/Animation.SuwWindowAnimation true diff --git a/library/full-support/res/layout/suw_glif_recycler_template_card.xml b/library/full-support/res/layout/suw_glif_recycler_template_card.xml index 9714ea4..cf67bd9 100644 --- a/library/full-support/res/layout/suw_glif_recycler_template_card.xml +++ b/library/full-support/res/layout/suw_glif_recycler_template_card.xml @@ -16,9 +16,11 @@ --> + android:layout_height="match_parent" + android:fitsSystemWindows="true"> + + + + + + + diff --git a/library/full-support/res/values/layouts.xml b/library/full-support/res/values/layouts.xml index 631fe5a..291d8d7 100644 --- a/library/full-support/res/values/layouts.xml +++ b/library/full-support/res/values/layouts.xml @@ -1,4 +1,4 @@ - + + android:layout_height="match_parent" + android:fitsSystemWindows="true"> + + + + + + + diff --git a/library/main/res/layout/suw_glif_template_card.xml b/library/main/res/layout/suw_glif_template_card.xml index 98f5547..d1d354f 100644 --- a/library/main/res/layout/suw_glif_template_card.xml +++ b/library/main/res/layout/suw_glif_template_card.xml @@ -16,9 +16,11 @@ --> + android:layout_height="match_parent" + android:fitsSystemWindows="true"> + + + + + + + diff --git a/library/main/res/values/attrs.xml b/library/main/res/values/attrs.xml index 1ac8745..1ebe286 100644 --- a/library/main/res/values/attrs.xml +++ b/library/main/res/values/attrs.xml @@ -60,6 +60,10 @@ + + + + diff --git a/library/main/res/values/layouts.xml b/library/main/res/values/layouts.xml index 7b43110..e4d3327 100644 --- a/library/main/res/values/layouts.xml +++ b/library/main/res/values/layouts.xml @@ -24,8 +24,8 @@ @layout/suw_no_scroll_template_header @layout/suw_no_scroll_template_header_collapsed - @layout/suw_glif_template_content - @layout/suw_glif_list_template_content + @layout/suw_glif_template_compact + @layout/suw_glif_list_template_compact diff --git a/library/main/src/com/android/setupwizardlib/GlifLayout.java b/library/main/src/com/android/setupwizardlib/GlifLayout.java index 294cc43..f1ec6d4 100644 --- a/library/main/src/com/android/setupwizardlib/GlifLayout.java +++ b/library/main/src/com/android/setupwizardlib/GlifLayout.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Build.VERSION_CODES; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -30,6 +31,8 @@ import android.widget.ImageView; import android.widget.ScrollView; import android.widget.TextView; +import com.android.setupwizardlib.view.StatusBarBackgroundLayout; + /** * Layout for the GLIF theme used in Setup Wizard for N. * @@ -103,6 +106,19 @@ public class GlifLayout extends TemplateLayout { } a.recycle(); + + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); + final View patternBg = findViewById(R.id.suw_pattern_bg); + if (patternBg != null) { + final GlifPatternDrawable background = GlifPatternDrawable.getDefault(getContext()); + if (patternBg instanceof StatusBarBackgroundLayout) { + ((StatusBarBackgroundLayout) patternBg).setStatusBarBackground(background); + } else { + patternBg.setBackground(background); + } + } + } } @Override diff --git a/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java b/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java new file mode 100644 index 0000000..23db40c --- /dev/null +++ b/library/main/src/com/android/setupwizardlib/GlifPatternDrawable.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2015 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.setupwizardlib; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.Build; + +import com.android.setupwizardlib.annotations.VisibleForTesting; + +public class GlifPatternDrawable extends Drawable { + + /* static section */ + + @SuppressLint("InlinedApi") + private static final int[] ATTRS_PRIMARY_COLOR = new int[]{ android.R.attr.colorPrimary }; + + private static final float VIEWBOX_HEIGHT = 768f; + private static final float VIEWBOX_WIDTH = 1366f; + private static final float SCALE_FOCUS_X = 200f; + private static final float SCALE_FOCUS_Y = 175f; + + public static GlifPatternDrawable getDefault(Context context) { + int colorPrimary = 0; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + final TypedArray a = context.obtainStyledAttributes(ATTRS_PRIMARY_COLOR); + colorPrimary = a.getColor(0, Color.BLACK); + a.recycle(); + } + return new GlifPatternDrawable(colorPrimary); + } + + /* non-static section */ + + private int mColor; + private Paint mPaint; + private float[] mTempHsv = new float[3]; + private Path mTempPath = new Path(); + + public GlifPatternDrawable(int color) { + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + setColor(color); + } + + @Override + public void draw(Canvas canvas) { + canvas.save(); + canvas.clipRect(getBounds()); + + Color.colorToHSV(mColor, mTempHsv); + scaleCanvasToBounds(canvas); + + // Draw the pattern by creating the paths, adjusting the colors and drawing them. The path + // values are extracted from the SVG of the pattern file. + + mTempHsv[2] = 0.753f; + Path p = mTempPath; + p.reset(); + p.moveTo(1029.4f, 357.5f); + p.lineTo(1366f, 759.1f); + p.lineTo(1366f, 0f); + p.lineTo(1137.7f, 0f); + p.close(); + drawPath(canvas, p, mTempHsv); + + mTempHsv[2] = 0.78f; + p.reset(); + p.moveTo(1138.1f, 0f); + p.rLineTo(-144.8f, 768f); + p.rLineTo(372.7f, 0f); + p.rLineTo(0f, -524f); + p.cubicTo(1290.7f, 121.6f, 1219.2f, 41.1f, 1178.7f, 0f); + p.close(); + drawPath(canvas, p, mTempHsv); + + mTempHsv[2] = 0.804f; + p.reset(); + p.moveTo(949.8f, 768f); + p.rCubicTo(92.6f, -170.6f, 213f, -440.3f, 269.4f, -768f); + p.lineTo(585f, 0f); + p.rLineTo(2.1f, 766f); + p.close(); + drawPath(canvas, p, mTempHsv); + + mTempHsv[2] = 0.827f; + p.reset(); + p.moveTo(471.1f, 768f); + p.rMoveTo(704.5f, 0f); + p.cubicTo(1123.6f, 563.3f, 1027.4f, 275.2f, 856.2f, 0f); + p.lineTo(476.4f, 0f); + p.rLineTo(-5.3f, 768f); + p.close(); + drawPath(canvas, p, mTempHsv); + + mTempHsv[2] = 0.867f; + p.reset(); + p.moveTo(323.1f, 768f); + p.moveTo(777.5f, 768f); + p.cubicTo(661.9f, 348.8f, 427.2f, 21.4f, 401.2f, 25.4f); + p.lineTo(323.1f, 768f); + p.close(); + drawPath(canvas, p, mTempHsv); + + mTempHsv[2] = 0.894f; + p.reset(); + p.moveTo(178.44286f, 766.85714f); + p.lineTo(308.7f, 768f); + p.cubicTo(381.7f, 604.6f, 481.6f, 344.3f, 562.2f, 0f); + p.lineTo(0f, 0f); + p.close(); + drawPath(canvas, p, mTempHsv); + + mTempHsv[2] = 0.929f; + p.reset(); + p.moveTo(146f, 0f); + p.lineTo(0f, 0f); + p.lineTo(0f, 768f); + p.lineTo(394.2f, 768f); + p.cubicTo(327.7f, 475.3f, 228.5f, 201f, 146f, 0f); + p.close(); + drawPath(canvas, p, mTempHsv); + + canvas.restore(); + } + + @VisibleForTesting + public void scaleCanvasToBounds(Canvas canvas) { + final Rect bounds = getBounds(); + final int height = bounds.height(); + final int width = bounds.width(); + + float scaleY = height / VIEWBOX_HEIGHT; + float scaleX = width / VIEWBOX_WIDTH; + // First scale both sides to fit independently. + canvas.scale(scaleX, scaleY); + if (scaleY > scaleX) { + // Adjust x-scale to maintain aspect ratio, but using (200, 0) as the pivot instead, + // so that more of the texture and less of the blank space on the left edge is seen. + canvas.scale(scaleY / scaleX, 1f, SCALE_FOCUS_X, 0f); + } else { + // Adjust y-scale to maintain aspect ratio, but using (0, 175) as the pivot instead, + // so that an intersection of two "circles" can always be seen. + canvas.scale(1f, scaleX / scaleY, 0f, SCALE_FOCUS_Y); + } + } + + private void drawPath(Canvas canvas, Path path, float[] hsv) { + mPaint.setColor(Color.HSVToColor(hsv)); + canvas.drawPath(path, mPaint); + } + + @Override + public void setAlpha(int i) { + // Ignore + } + + @Override + public void setColorFilter(ColorFilter colorFilter) { + // Ignore + } + + @Override + public int getOpacity() { + return 0; + } + + public void setColor(int color) { + mColor = color; + mPaint.setColor(color); + invalidateSelf(); + } + + public int getColor() { + return mColor; + } +} diff --git a/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java b/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java new file mode 100644 index 0000000..7fdabec --- /dev/null +++ b/library/main/src/com/android/setupwizardlib/view/StatusBarBackgroundLayout.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2015 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.setupwizardlib.view; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Build.VERSION_CODES; +import android.util.AttributeSet; +import android.view.WindowInsets; +import android.widget.FrameLayout; + +import com.android.setupwizardlib.R; + +/** + * A FrameLayout subclass that will responds to onApplyWindowInsets to draw a drawable in the top + * inset area, making a background effect for the navigation bar. To make use of this layout, + * specify the system UI visibility {@link android.view.View#SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN} and + * set specify fitsSystemWindows. + * + *

This view is a normal FrameLayout if either of those are not set, or if the platform version + * is lower than Lollipop. + */ +public class StatusBarBackgroundLayout extends FrameLayout { + + private Drawable mStatusBarBackground; + private Object mLastInsets; // Use generic Object type for compatibility + + public StatusBarBackgroundLayout(Context context) { + super(context); + init(context, null, 0); + } + + public StatusBarBackgroundLayout(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs, 0); + } + + @TargetApi(VERSION_CODES.HONEYCOMB) + public StatusBarBackgroundLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs, defStyleAttr); + } + + private void init(Context context, AttributeSet attrs, int defStyleAttr) { + final TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.SuwStatusBarBackgroundLayout, defStyleAttr, 0); + final Drawable statusBarBackground = + a.getDrawable(R.styleable.SuwStatusBarBackgroundLayout_suwStatusBarBackground); + setStatusBarBackground(statusBarBackground); + a.recycle(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + if (mLastInsets != null) { + final int insetTop = ((WindowInsets) mLastInsets).getSystemWindowInsetTop(); + if (insetTop > 0) { + mStatusBarBackground.setBounds(0, 0, getWidth(), insetTop); + mStatusBarBackground.draw(canvas); + } + } + } + } + + public void setStatusBarBackground(Drawable background) { + mStatusBarBackground = background; + if (Build.VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { + setWillNotDraw(background == null); + setFitsSystemWindows(background != null); + invalidate(); + } + } + + public Drawable getStatusBarBackground() { + return mStatusBarBackground; + } + + @Override + public WindowInsets onApplyWindowInsets(WindowInsets insets) { + mLastInsets = insets; + return super.onApplyWindowInsets(insets); + } +} diff --git a/library/test/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java b/library/test/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java new file mode 100644 index 0000000..d53f49c --- /dev/null +++ b/library/test/src/com/android/setupwizardlib/test/GlifPatternDrawableTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2015 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.setupwizardlib.test; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.setupwizardlib.GlifPatternDrawable; + +public class GlifPatternDrawableTest extends AndroidTestCase { + + @SmallTest + public void testScaleToCanvasSquare() { + final Canvas canvas = new Canvas(); + Matrix expected = new Matrix(canvas.getMatrix()); + + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 683, 384); // half each side of the view box + drawable.scaleCanvasToBounds(canvas); + + expected.postScale(0.5f, 0.5f); + + assertEquals("Matrices should match", expected, canvas.getMatrix()); + } + + @SmallTest + public void testScaleToCanvasTall() { + final Canvas canvas = new Canvas(); + final Matrix expected = new Matrix(canvas.getMatrix()); + + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 683, 768); // half the width only + drawable.scaleCanvasToBounds(canvas); + + expected.postScale(1f, 1f); + expected.postTranslate(-100f, 0f); + + assertEquals("Matrices should match", expected, canvas.getMatrix()); + } + + @SmallTest + public void testScaleToCanvasWide() { + final Canvas canvas = new Canvas(); + final Matrix expected = new Matrix(canvas.getMatrix()); + + final GlifPatternDrawable drawable = new GlifPatternDrawable(Color.RED); + drawable.setBounds(0, 0, 1366, 384); // half the height only + drawable.scaleCanvasToBounds(canvas); + + expected.postScale(1f, 1f); + expected.postTranslate(0f, -87.5f); + + assertEquals("Matrices should match", expected, canvas.getMatrix()); + } +} diff --git a/library/test/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java b/library/test/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java new file mode 100644 index 0000000..fe680e4 --- /dev/null +++ b/library/test/src/com/android/setupwizardlib/test/StatusBarBackgroundLayoutTest.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 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.setupwizardlib.test; + +import android.graphics.drawable.ShapeDrawable; +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import com.android.setupwizardlib.view.StatusBarBackgroundLayout; + +public class StatusBarBackgroundLayoutTest extends AndroidTestCase { + + @SmallTest + public void testSetStatusBarBackground() { + final StatusBarBackgroundLayout layout = new StatusBarBackgroundLayout(getContext()); + final ShapeDrawable drawable = new ShapeDrawable(); + layout.setStatusBarBackground(drawable); + assertSame("Status bar background drawable should be same as set", + drawable, layout.getStatusBarBackground()); + } +}