1 /**
2  * Copyright (C) 2022 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.settingslib.qrcode;
18 
19 import android.content.Context;
20 import android.graphics.Bitmap;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.PorterDuff;
24 import android.graphics.PorterDuffXfermode;
25 import android.graphics.RectF;
26 import android.util.AttributeSet;
27 import android.util.TypedValue;
28 import android.view.View;
29 
30 import com.android.settingslib.R;
31 
32 public class QrDecorateView extends View {
33     private static final float CORNER_STROKE_WIDTH = 4f;    // 4dp
34     private static final float CORNER_LINE_LENGTH = 264f;   // 264dp
35     private static final float CORNER_RADIUS = 16f;         // 16dp
36 
37     private final int mCornerColor;
38     private final int mFocusedCornerColor;
39     private final int mBackgroundColor;
40 
41     private final Paint mStrokePaint;
42     private final Paint mTransparentPaint;
43     private final Paint mBackgroundPaint;
44 
45     private final float mRadius;
46     private final float mInnerRadius;
47 
48     private Bitmap mMaskBitmap;
49     private Canvas mMaskCanvas;
50 
51     private RectF mOuterFrame;
52     private RectF mInnerFrame;
53 
54     private boolean mFocused;
55 
QrDecorateView(Context context)56     public QrDecorateView(Context context) {
57         this(context, null);
58     }
59 
QrDecorateView(Context context, AttributeSet attrs)60     public QrDecorateView(Context context, AttributeSet attrs) {
61         this(context, attrs, 0);
62     }
63 
QrDecorateView(Context context, AttributeSet attrs, int defStyleAttr)64     public QrDecorateView(Context context, AttributeSet attrs, int defStyleAttr) {
65         this(context, attrs, defStyleAttr, 0);
66     }
67 
QrDecorateView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)68     public QrDecorateView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
69         super(context, attrs, defStyleAttr, defStyleRes);
70 
71         mFocused = false;
72         mRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS,
73                 getResources().getDisplayMetrics());
74         // Inner radius needs to minus stroke width for keeping the width of border consistent.
75         mInnerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
76                 CORNER_RADIUS - CORNER_STROKE_WIDTH, getResources().getDisplayMetrics());
77 
78         mCornerColor = context.getResources().getColor(R.color.qr_corner_line_color);
79         mFocusedCornerColor = context.getResources().getColor(R.color.qr_focused_corner_line_color);
80         mBackgroundColor = context.getResources().getColor(R.color.qr_background_color);
81 
82         mStrokePaint = new Paint();
83         mStrokePaint.setAntiAlias(true);
84 
85         mTransparentPaint = new Paint();
86         mTransparentPaint.setAntiAlias(true);
87         mTransparentPaint.setColor(getResources().getColor(android.R.color.transparent));
88         mTransparentPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
89 
90         mBackgroundPaint = new Paint();
91         mBackgroundPaint.setColor(mBackgroundColor);
92     }
93 
94     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)95     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
96         super.onLayout(changed, left, top, right, bottom);
97 
98         if (mMaskBitmap == null) {
99             mMaskBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
100             mMaskCanvas = new Canvas(mMaskBitmap);
101         }
102 
103         calculateFramePos();
104     }
105 
106     @Override
onDraw(Canvas canvas)107     protected void onDraw(Canvas canvas) {
108         if (mMaskCanvas != null && mMaskBitmap != null) {
109             // Set frame line color.
110             mStrokePaint.setColor(mFocused ? mFocusedCornerColor : mCornerColor);
111             // Draw background color.
112             mMaskCanvas.drawColor(mBackgroundColor);
113             // Draw outer corner.
114             mMaskCanvas.drawRoundRect(mOuterFrame, mRadius, mRadius, mStrokePaint);
115             // Draw inner transparent corner.
116             mMaskCanvas.drawRoundRect(mInnerFrame, mInnerRadius, mInnerRadius, mTransparentPaint);
117 
118             canvas.drawBitmap(mMaskBitmap, 0, 0, mBackgroundPaint);
119         }
120         super.onDraw(canvas);
121     }
122 
calculateFramePos()123     private void calculateFramePos() {
124         final int centralX = getWidth() / 2;
125         final int centralY = getHeight() / 2;
126         final float cornerLineLength = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
127                 CORNER_LINE_LENGTH, getResources().getDisplayMetrics()) / 2;
128         final float strokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
129                 CORNER_STROKE_WIDTH, getResources().getDisplayMetrics());
130 
131         mOuterFrame = new RectF(centralX - cornerLineLength, centralY - cornerLineLength,
132                 centralX + cornerLineLength, centralY + cornerLineLength);
133         mInnerFrame = new RectF(mOuterFrame.left + strokeWidth, mOuterFrame.top + strokeWidth,
134                 mOuterFrame.right - strokeWidth, mOuterFrame.bottom - strokeWidth);
135     }
136 
137     // Draws green lines if focused. Otherwise, draws white lines.
setFocused(boolean focused)138     public void setFocused(boolean focused) {
139         mFocused = focused;
140         invalidate();
141     }
142 }
143