• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.setupwizardlib;
18 
19 import android.annotation.SuppressLint;
20 import android.content.Context;
21 import android.content.res.TypedArray;
22 import android.graphics.Bitmap;
23 import android.graphics.Canvas;
24 import android.graphics.Color;
25 import android.graphics.ColorFilter;
26 import android.graphics.Paint;
27 import android.graphics.Path;
28 import android.graphics.PixelFormat;
29 import android.graphics.PorterDuff;
30 import android.graphics.PorterDuffXfermode;
31 import android.graphics.Rect;
32 import android.graphics.drawable.Drawable;
33 import android.os.Build;
34 import android.support.annotation.NonNull;
35 import android.support.annotation.VisibleForTesting;
36 
37 import java.lang.ref.SoftReference;
38 
39 /**
40  * This class draws the GLIF pattern used as the status bar background for phones and background for
41  * tablets in GLIF layout.
42  */
43 public class GlifPatternDrawable extends Drawable {
44     /*
45      * This class essentially implements a simple SVG in Java code, with some special handling of
46      * scaling when given different bounds.
47      */
48 
49     /* static section */
50 
51     @SuppressLint("InlinedApi")
52     private static final int[] ATTRS_PRIMARY_COLOR = new int[]{ android.R.attr.colorPrimary };
53 
54     private static final float VIEWBOX_HEIGHT = 768f;
55     private static final float VIEWBOX_WIDTH = 1366f;
56     // X coordinate of scale focus, as a fraction of of the width. (Range is 0 - 1)
57     private static final float SCALE_FOCUS_X = .146f;
58     // Y coordinate of scale focus, as a fraction of of the height. (Range is 0 - 1)
59     private static final float SCALE_FOCUS_Y = .228f;
60 
61     // Alpha component of the color to be drawn, on top of the grayscale pattern. (Range is 0 - 1)
62     private static final float COLOR_ALPHA = .8f;
63     // Int version of COLOR_ALPHA. (Range is 0 - 255)
64     private static final int COLOR_ALPHA_INT = (int) (COLOR_ALPHA * 255);
65 
66     // Cap the bitmap size, such that it won't hurt the performance too much
67     // and it won't crash due to a very large scale.
68     // The drawable will look blurry above this size.
69     // This is a multiplier applied on top of the viewbox size.
70     // Resulting max cache size = (1.5 x 1366, 1.5 x 768) = (2049, 1152)
71     private static final float MAX_CACHED_BITMAP_SCALE = 1.5f;
72 
73     private static final int NUM_PATHS = 7;
74 
75     private static SoftReference<Bitmap> sBitmapCache;
76     private static Path[] sPatternPaths;
77     private static int[] sPatternLightness;
78 
getDefault(Context context)79     public static GlifPatternDrawable getDefault(Context context) {
80         int colorPrimary = 0;
81         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
82             final TypedArray a = context.obtainStyledAttributes(ATTRS_PRIMARY_COLOR);
83             colorPrimary = a.getColor(0, Color.BLACK);
84             a.recycle();
85         }
86         return new GlifPatternDrawable(colorPrimary);
87     }
88 
89     @VisibleForTesting
invalidatePattern()90     public static void invalidatePattern() {
91         sBitmapCache = null;
92     }
93 
94     /* non-static section */
95 
96     private int mColor;
97     private Paint mTempPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
98 
GlifPatternDrawable(int color)99     public GlifPatternDrawable(int color) {
100         setColor(color);
101     }
102 
103     @Override
draw(@onNull Canvas canvas)104     public void draw(@NonNull Canvas canvas) {
105         final Rect bounds = getBounds();
106         int drawableWidth = bounds.width();
107         int drawableHeight = bounds.height();
108         Bitmap bitmap = null;
109         if (sBitmapCache != null) {
110             bitmap = sBitmapCache.get();
111         }
112         if (bitmap != null) {
113             final int bitmapWidth = bitmap.getWidth();
114             final int bitmapHeight = bitmap.getHeight();
115             // Invalidate the cache if this drawable is bigger and we can still create a bigger
116             // cache.
117             if (drawableWidth > bitmapWidth
118                     && bitmapWidth < VIEWBOX_WIDTH * MAX_CACHED_BITMAP_SCALE) {
119                 bitmap = null;
120             } else if (drawableHeight > bitmapHeight
121                     && bitmapHeight < VIEWBOX_HEIGHT * MAX_CACHED_BITMAP_SCALE) {
122                 bitmap = null;
123             }
124         }
125 
126         if (bitmap == null) {
127             // Reset the paint so it can be used to draw the paths in renderOnCanvas
128             mTempPaint.reset();
129 
130             bitmap = createBitmapCache(drawableWidth, drawableHeight);
131             sBitmapCache = new SoftReference<>(bitmap);
132 
133             // Reset the paint to so it can be used to draw the bitmap
134             mTempPaint.reset();
135         }
136 
137         canvas.save();
138         canvas.clipRect(bounds);
139 
140         scaleCanvasToBounds(canvas, bitmap, bounds);
141         canvas.drawColor(Color.BLACK);
142         mTempPaint.setColor(Color.WHITE);
143         canvas.drawBitmap(bitmap, 0, 0, mTempPaint);
144         canvas.drawColor(mColor);
145 
146         canvas.restore();
147     }
148 
149     @VisibleForTesting
createBitmapCache(int drawableWidth, int drawableHeight)150     public Bitmap createBitmapCache(int drawableWidth, int drawableHeight) {
151         float scaleX = drawableWidth / VIEWBOX_WIDTH;
152         float scaleY = drawableHeight / VIEWBOX_HEIGHT;
153         float scale = Math.max(scaleX, scaleY);
154         scale = Math.min(MAX_CACHED_BITMAP_SCALE, scale);
155 
156 
157         int scaledWidth = (int) (VIEWBOX_WIDTH * scale);
158         int scaledHeight = (int) (VIEWBOX_HEIGHT * scale);
159 
160         // Use ALPHA_8 mask to save memory, since the pattern is grayscale only anyway.
161         Bitmap bitmap = Bitmap.createBitmap(
162                 scaledWidth,
163                 scaledHeight,
164                 Bitmap.Config.ALPHA_8);
165         Canvas bitmapCanvas = new Canvas(bitmap);
166         renderOnCanvas(bitmapCanvas, scale);
167         return bitmap;
168     }
169 
renderOnCanvas(Canvas canvas, float scale)170     private void renderOnCanvas(Canvas canvas, float scale) {
171         canvas.save();
172         canvas.scale(scale, scale);
173 
174         mTempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
175 
176         // Draw the pattern by creating the paths, adjusting the colors and drawing them. The path
177         // values are extracted from the SVG of the pattern file.
178 
179         if (sPatternPaths == null) {
180             sPatternPaths = new Path[NUM_PATHS];
181             // Lightness values of the pattern, range 0 - 255
182             sPatternLightness = new int[] { 10, 40, 51, 66, 91, 112, 130 };
183 
184             Path p = sPatternPaths[0] = new Path();
185             p.moveTo(1029.4f, 357.5f);
186             p.lineTo(1366f, 759.1f);
187             p.lineTo(1366f, 0f);
188             p.lineTo(1137.7f, 0f);
189             p.close();
190 
191             p = sPatternPaths[1] = new Path();
192             p.moveTo(1138.1f, 0f);
193             p.rLineTo(-144.8f, 768f);
194             p.rLineTo(372.7f, 0f);
195             p.rLineTo(0f, -524f);
196             p.cubicTo(1290.7f, 121.6f, 1219.2f, 41.1f, 1178.7f, 0f);
197             p.close();
198 
199             p = sPatternPaths[2] = new Path();
200             p.moveTo(949.8f, 768f);
201             p.rCubicTo(92.6f, -170.6f, 213f, -440.3f, 269.4f, -768f);
202             p.lineTo(585f, 0f);
203             p.rLineTo(2.1f, 766f);
204             p.close();
205 
206             p = sPatternPaths[3] = new Path();
207             p.moveTo(471.1f, 768f);
208             p.rMoveTo(704.5f, 0f);
209             p.cubicTo(1123.6f, 563.3f, 1027.4f, 275.2f, 856.2f, 0f);
210             p.lineTo(476.4f, 0f);
211             p.rLineTo(-5.3f, 768f);
212             p.close();
213 
214             p = sPatternPaths[4] = new Path();
215             p.moveTo(323.1f, 768f);
216             p.moveTo(777.5f, 768f);
217             p.cubicTo(661.9f, 348.8f, 427.2f, 21.4f, 401.2f, 25.4f);
218             p.lineTo(323.1f, 768f);
219             p.close();
220 
221             p = sPatternPaths[5] = new Path();
222             p.moveTo(178.44286f, 766.8571f);
223             p.lineTo(308.7f, 768f);
224             p.cubicTo(381.7f, 604.6f, 481.6f, 344.3f, 562.2f, 0f);
225             p.lineTo(0f, 0f);
226             p.close();
227 
228             p = sPatternPaths[6] = new Path();
229             p.moveTo(146f, 0f);
230             p.lineTo(0f, 0f);
231             p.lineTo(0f, 768f);
232             p.lineTo(394.2f, 768f);
233             p.cubicTo(327.7f, 475.3f, 228.5f, 201f, 146f, 0f);
234             p.close();
235         }
236 
237         for (int i = 0; i < NUM_PATHS; i++) {
238             // Color is 0xAARRGGBB, so alpha << 24 will create a color with (alpha)% black.
239             // Although the color components don't really matter, since the backing bitmap cache is
240             // ALPHA_8.
241             mTempPaint.setColor(sPatternLightness[i] << 24);
242             canvas.drawPath(sPatternPaths[i], mTempPaint);
243         }
244 
245         canvas.restore();
246         mTempPaint.reset();
247     }
248 
249     @VisibleForTesting
scaleCanvasToBounds(Canvas canvas, Bitmap bitmap, Rect drawableBounds)250     public void scaleCanvasToBounds(Canvas canvas, Bitmap bitmap, Rect drawableBounds) {
251         int bitmapWidth = bitmap.getWidth();
252         int bitmapHeight = bitmap.getHeight();
253         float scaleX = drawableBounds.width() / (float) bitmapWidth;
254         float scaleY = drawableBounds.height() / (float) bitmapHeight;
255 
256         // First scale both sides to fit independently.
257         canvas.scale(scaleX, scaleY);
258         if (scaleY > scaleX) {
259             // Adjust x-scale to maintain aspect ratio using the pivot, so that more of the texture
260             // and less of the blank space on the left edge is seen.
261             canvas.scale(scaleY / scaleX, 1f, SCALE_FOCUS_X * bitmapWidth, 0f);
262         } else if (scaleX > scaleY) {
263             // Adjust y-scale to maintain aspect ratio using the pivot, so that an intersection of
264             // two "circles" can always be seen.
265             canvas.scale(1f, scaleX / scaleY, 0f, SCALE_FOCUS_Y * bitmapHeight);
266         }
267     }
268 
269     @Override
setAlpha(int i)270     public void setAlpha(int i) {
271         // Ignore
272     }
273 
274     @Override
setColorFilter(ColorFilter colorFilter)275     public void setColorFilter(ColorFilter colorFilter) {
276         // Ignore
277     }
278 
279     @Override
getOpacity()280     public int getOpacity() {
281         return PixelFormat.UNKNOWN;
282     }
283 
284     /**
285      * Sets the color used as the base color of this pattern drawable. The alpha component of the
286      * color will be ignored.
287      */
setColor(int color)288     public void setColor(int color) {
289         final int r = Color.red(color);
290         final int g = Color.green(color);
291         final int b = Color.blue(color);
292         mColor = Color.argb(COLOR_ALPHA_INT, r, g, b);
293         invalidateSelf();
294     }
295 
296     /**
297      * @return The color used as the base color of this pattern drawable. The alpha component of
298      * this is always 255.
299      */
getColor()300     public int getColor() {
301         return Color.argb(255, Color.red(mColor), Color.green(mColor), Color.blue(mColor));
302     }
303 }
304