1 /*
2  * Copyright (C) 2023 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.launcher3.apppairs;
18 
19 import android.graphics.Canvas;
20 import android.graphics.ColorFilter;
21 import android.graphics.Paint;
22 import android.graphics.PixelFormat;
23 import android.graphics.RectF;
24 import android.graphics.drawable.Drawable;
25 import android.os.Build;
26 
27 import androidx.annotation.NonNull;
28 
29 import com.android.launcher3.icons.FastBitmapDrawable;
30 
31 /**
32  * A composed Drawable consisting of the two app pair icons and the background behind them (looks
33  * like two rectangles).
34  */
35 public class AppPairIconDrawable extends Drawable {
36     private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
37     private final AppPairIconDrawingParams mP;
38     private final FastBitmapDrawable mIcon1;
39     private final FastBitmapDrawable mIcon2;
40 
41     /**
42      * Null values to use with
43      * {@link Canvas#drawDoubleRoundRect(RectF, float[], RectF, float[], Paint)}, since there
44      * doesn't seem to be any other API for drawing rectangles with 4 different corner radii.
45      */
46     private static final RectF EMPTY_RECT = new RectF();
47     private static final float[] ARRAY_OF_ZEROES = new float[8];
48 
AppPairIconDrawable( AppPairIconDrawingParams p, FastBitmapDrawable icon1, FastBitmapDrawable icon2)49     AppPairIconDrawable(
50             AppPairIconDrawingParams p, FastBitmapDrawable icon1, FastBitmapDrawable icon2) {
51         mP = p;
52         mBackgroundPaint.setStyle(Paint.Style.FILL);
53         mBackgroundPaint.setColor(p.getBgColor());
54         mIcon1 = icon1;
55         mIcon2 = icon2;
56     }
57 
58     @Override
draw(@onNull Canvas canvas)59     public void draw(@NonNull Canvas canvas) {
60         if (mP.isLeftRightSplit()) {
61             drawLeftRightSplit(canvas);
62         } else {
63             drawTopBottomSplit(canvas);
64         }
65 
66         canvas.translate(
67                 mP.getStandardIconPadding() + mP.getOuterPadding(),
68                 mP.getStandardIconPadding() + mP.getOuterPadding()
69         );
70 
71         // Draw first icon.
72         canvas.save();
73         // The app icons are placed differently depending on device orientation.
74         if (mP.isLeftRightSplit()) {
75             canvas.translate(
76                     mP.getInnerPadding(),
77                     mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f
78             );
79         } else {
80             canvas.translate(
81                     mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f,
82                     mP.getInnerPadding()
83             );
84         }
85 
86         mIcon1.draw(canvas);
87         canvas.restore();
88 
89         // Draw second icon.
90         canvas.save();
91         // The app icons are placed differently depending on device orientation.
92         if (mP.isLeftRightSplit()) {
93             canvas.translate(
94                     mP.getBackgroundSize() - (mP.getInnerPadding() + mP.getMemberIconSize()),
95                     mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f
96             );
97         } else {
98             canvas.translate(
99                     mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f,
100                     mP.getBackgroundSize() - (mP.getInnerPadding() + mP.getMemberIconSize())
101             );
102         }
103 
104         mIcon2.draw(canvas);
105         canvas.restore();
106     }
107 
108     /**
109      * When device is in landscape, we draw the rectangles with a left-right split.
110      */
drawLeftRightSplit(Canvas canvas)111     private void drawLeftRightSplit(Canvas canvas) {
112         // Get the bounds where we will draw the background image
113         int width = mP.getIconSize();
114         int height = mP.getIconSize();
115 
116         // The left half of the background image, excluding center channel
117         RectF leftSide = new RectF(
118                 mP.getStandardIconPadding() + mP.getOuterPadding(),
119                 mP.getStandardIconPadding() + mP.getOuterPadding(),
120                 (width / 2f) - (mP.getCenterChannelSize() / 2f),
121                 height - (mP.getStandardIconPadding() + mP.getOuterPadding())
122         );
123         // The right half of the background image, excluding center channel
124         RectF rightSide = new RectF(
125                 (width / 2f) + (mP.getCenterChannelSize() / 2f),
126                 (mP.getStandardIconPadding() + mP.getOuterPadding()),
127                 width - (mP.getStandardIconPadding() + mP.getOuterPadding()),
128                 height - (mP.getStandardIconPadding() + mP.getOuterPadding())
129         );
130 
131         drawCustomRoundedRect(canvas, leftSide, new float[]{
132                 mP.getBigRadius(), mP.getBigRadius(),
133                 mP.getSmallRadius(), mP.getSmallRadius(),
134                 mP.getSmallRadius(), mP.getSmallRadius(),
135                 mP.getBigRadius(), mP.getBigRadius()});
136         drawCustomRoundedRect(canvas, rightSide, new float[]{
137                 mP.getSmallRadius(), mP.getSmallRadius(),
138                 mP.getBigRadius(), mP.getBigRadius(),
139                 mP.getBigRadius(), mP.getBigRadius(),
140                 mP.getSmallRadius(), mP.getSmallRadius()});
141     }
142 
143     /**
144      * When device is in portrait, we draw the rectangles with a top-bottom split.
145      */
drawTopBottomSplit(Canvas canvas)146     private void drawTopBottomSplit(Canvas canvas) {
147         // Get the bounds where we will draw the background image
148         int width = mP.getIconSize();
149         int height = mP.getIconSize();
150 
151         // The top half of the background image, excluding center channel
152         RectF topSide = new RectF(
153                 (mP.getStandardIconPadding() + mP.getOuterPadding()),
154                 (mP.getStandardIconPadding() + mP.getOuterPadding()),
155                 width - (mP.getStandardIconPadding() + mP.getOuterPadding()),
156                 (height / 2f) - (mP.getCenterChannelSize() / 2f)
157         );
158         // The bottom half of the background image, excluding center channel
159         RectF bottomSide = new RectF(
160                 (mP.getStandardIconPadding() + mP.getOuterPadding()),
161                 (height / 2f) + (mP.getCenterChannelSize() / 2f),
162                 width - (mP.getStandardIconPadding() + mP.getOuterPadding()),
163                 height - (mP.getStandardIconPadding() + mP.getOuterPadding())
164         );
165 
166         drawCustomRoundedRect(canvas, topSide, new float[]{
167                 mP.getBigRadius(), mP.getBigRadius(),
168                 mP.getBigRadius(), mP.getBigRadius(),
169                 mP.getSmallRadius(), mP.getSmallRadius(),
170                 mP.getSmallRadius(), mP.getSmallRadius()});
171         drawCustomRoundedRect(canvas, bottomSide, new float[]{
172                 mP.getSmallRadius(), mP.getSmallRadius(),
173                 mP.getSmallRadius(), mP.getSmallRadius(),
174                 mP.getBigRadius(), mP.getBigRadius(),
175                 mP.getBigRadius(), mP.getBigRadius()});
176     }
177 
178     /**
179      * Draws a rectangle with custom rounded corners.
180      * @param c The Canvas to draw on.
181      * @param rect The bounds of the rectangle.
182      * @param radii An array of 8 radii for the corners: top left x, top left y, top right x, top
183      *              right y, bottom right x, and so on.
184      */
drawCustomRoundedRect(Canvas c, RectF rect, float[] radii)185     private void drawCustomRoundedRect(Canvas c, RectF rect, float[] radii) {
186         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
187             // Canvas.drawDoubleRoundRect is supported from Q onward
188             c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, mBackgroundPaint);
189         } else {
190             // Fallback rectangle with uniform rounded corners
191             c.drawRoundRect(rect, mP.getBigRadius(), mP.getBigRadius(), mBackgroundPaint);
192         }
193     }
194 
195     @Override
getOpacity()196     public int getOpacity() {
197         return PixelFormat.OPAQUE;
198     }
199 
200     @Override
setAlpha(int i)201     public void setAlpha(int i) {
202         mBackgroundPaint.setAlpha(i);
203     }
204 
205     @Override
setColorFilter(ColorFilter colorFilter)206     public void setColorFilter(ColorFilter colorFilter) {
207         mBackgroundPaint.setColorFilter(colorFilter);
208     }
209 
210     @Override
getIntrinsicWidth()211     public int getIntrinsicWidth() {
212         return mP.getIconSize();
213     }
214 
215     @Override
getIntrinsicHeight()216     public int getIntrinsicHeight() {
217         return mP.getIconSize();
218     }
219 }
220