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