Loading quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +2 −1 Original line number Diff line number Diff line Loading @@ -354,7 +354,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar break; case ITEM_TYPE_APP_PAIR: hotseatView = AppPairIcon.inflateIcon( expectedLayoutResId, mActivityContext, this, folderInfo); expectedLayoutResId, mActivityContext, this, folderInfo, BubbleTextView.DISPLAY_TASKBAR); ((AppPairIcon) hotseatView).setTextVisible(false); break; default: Loading src/com/android/launcher3/BubbleTextView.java +3 −4 Original line number Diff line number Diff line Loading @@ -58,7 +58,6 @@ import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; import com.android.launcher3.accessibility.BaseAccessibilityDelegate; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.dragndrop.DragOptions.PreDragCondition; import com.android.launcher3.dragndrop.DraggableView; Loading Loading @@ -96,10 +95,10 @@ import java.util.Locale; public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, IconLabelDotView, DraggableView, Reorderable { private static final int DISPLAY_WORKSPACE = 0; public static final int DISPLAY_WORKSPACE = 0; public static final int DISPLAY_ALL_APPS = 1; private static final int DISPLAY_FOLDER = 2; protected static final int DISPLAY_TASKBAR = 5; public static final int DISPLAY_FOLDER = 2; public static final int DISPLAY_TASKBAR = 5; public static final int DISPLAY_SEARCH_RESULT = 6; public static final int DISPLAY_SEARCH_RESULT_SMALL = 7; public static final int DISPLAY_PREDICTION_ROW = 8; Loading src/com/android/launcher3/apppairs/AppPairIcon.java +48 −19 Original line number Diff line number Diff line Loading @@ -16,13 +16,14 @@ package com.android.launcher3.apppairs; import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; Loading @@ -38,7 +39,6 @@ import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.MultiTranslateDelegate; import com.android.launcher3.views.ActivityContext; import java.util.Collections; import java.util.Comparator; import java.util.function.Predicate; Loading @@ -62,6 +62,9 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab private BubbleTextView mAppPairName; // The underlying ItemInfo that stores info about the app pair members, etc. private FolderInfo mInfo; // The containing element that holds this icon: workspace, taskbar, folder, etc. Affects certain // aspects of how the icon is drawn. private int mContainer; // Required for Reorderable -- handles translation and bouncing movements private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this); Loading @@ -79,7 +82,7 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab * Builds an AppPairIcon to be added to the Launcher. */ public static AppPairIcon inflateIcon(int resId, ActivityContext activity, @Nullable ViewGroup group, FolderInfo appPairInfo) { @Nullable ViewGroup group, FolderInfo appPairInfo, int container) { DeviceProfile grid = activity.getDeviceProfile(); LayoutInflater inflater = (group != null) ? LayoutInflater.from(group.getContext()) Loading @@ -87,30 +90,32 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab AppPairIcon icon = (AppPairIcon) inflater.inflate(resId, group, false); // Sort contents, so that left-hand app comes first Collections.sort(appPairInfo.contents, Comparator.comparingInt(a -> a.rank)); appPairInfo.contents.sort(Comparator.comparingInt(a -> a.rank)); icon.setClipToPadding(false); icon.setTag(appPairInfo); icon.setOnClickListener(activity.getItemOnClickListener()); icon.mInfo = appPairInfo; icon.mContainer = container; if (icon.mInfo.contents.size() != 2) { Log.wtf(TAG, "AppPair contents not 2, size: " + icon.mInfo.contents.size()); return icon; } icon.checkScreenSize(); icon.checkDisabledState(); // Set up icon drawable area icon.mIconGraphic = icon.findViewById(R.id.app_pair_icon_graphic); icon.mIconGraphic.init(activity, icon); icon.mIconGraphic.init(icon, container); // Set up app pair title icon.mAppPairName = icon.findViewById(R.id.app_pair_icon_name); icon.mAppPairName.setCompoundDrawablePadding(0); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mAppPairName.getLayoutParams(); lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx; // Shift the title text down to leave room for the icon graphic. Since the icon graphic is // a separate element (and not set as a CompoundDrawable on the BubbleTextView), we need to // shift the text down manually. lp.topMargin = container == DISPLAY_FOLDER ? grid.folderChildIconSizePx + grid.folderChildDrawablePaddingPx : grid.iconSizePx + grid.iconDrawablePaddingPx; // For some reason, app icons have setIncludeFontPadding(false) inside folders, so we set it // here to match that. icon.mAppPairName.setIncludeFontPadding(container != DISPLAY_FOLDER); icon.mAppPairName.setText(appPairInfo.title); // Set up accessibility Loading Loading @@ -174,7 +179,11 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab return mInfo; } public View getIconDrawableArea() { public BubbleTextView getTitleTextView() { return mAppPairName; } public AppPairIconGraphic getIconDrawableArea() { return mIconGraphic; } Loading @@ -194,12 +203,14 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab * {@link AppPairIconGraphic#dispatchDraw(Canvas)} or clicked on * {@link com.android.launcher3.touch.ItemClickHandler#onClickAppPairIcon(View)} */ public void checkScreenSize() { public void checkDisabledState() { DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile(); // If user is on a small screen, we can't launch if either of the apps is non-resizeable mIsLaunchableAtScreenSize = dp.isTablet || getInfo().contents.stream().noneMatch( wii -> wii.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE)); // Invalidate to update icons mIconGraphic.redraw(); } /** Loading @@ -209,8 +220,26 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab // If either of the app pair icons return true on the predicate (i.e. in the list of // updated apps), redraw the icon graphic (icon background and both icons). if (getInfo().contents.stream().anyMatch(itemCheck)) { checkScreenSize(); mIconGraphic.invalidate(); checkDisabledState(); } } /** * Inside folders, icons are vertically centered in their rows. See * {@link BubbleTextView#onMeasure(int, int)} for comparison. */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mContainer == DISPLAY_FOLDER) { int height = MeasureSpec.getSize(heightMeasureSpec); ActivityContext activity = ActivityContext.lookupContext(getContext()); Paint.FontMetrics fm = mAppPairName.getPaint().getFontMetrics(); int cellHeightPx = activity.getDeviceProfile().folderChildIconSizePx + activity.getDeviceProfile().folderChildDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top); setPadding(getPaddingLeft(), (height - cellHeightPx) / 2, getPaddingRight(), getPaddingBottom()); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } src/com/android/launcher3/apppairs/AppPairIconBackground.java +0 −166 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.launcher3.apppairs; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Build; import com.android.launcher3.R; /** * A Drawable for the background behind the twin app icons (looks like two rectangles). */ class AppPairIconBackground extends Drawable { // The underlying view that we are drawing this background on. private final AppPairIconGraphic icon; private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); /** * Null values to use with * {@link Canvas#drawDoubleRoundRect(RectF, float[], RectF, float[], Paint)}, since there * doesn't seem to be any other API for drawing rectangles with 4 different corner radii. */ private static final RectF EMPTY_RECT = new RectF(); private static final float[] ARRAY_OF_ZEROES = new float[8]; AppPairIconBackground(Context context, AppPairIconGraphic iconGraphic) { icon = iconGraphic; // Set up background paint color TypedArray ta = context.getTheme().obtainStyledAttributes(R.styleable.FolderIconPreview); mBackgroundPaint.setStyle(Paint.Style.FILL); mBackgroundPaint.setColor( ta.getColor(R.styleable.FolderIconPreview_folderPreviewColor, 0)); ta.recycle(); } @Override public void draw(Canvas canvas) { if (icon.isLeftRightSplit()) { drawLeftRightSplit(canvas); } else { drawTopBottomSplit(canvas); } } /** * When device is in landscape, we draw the rectangles with a left-right split. */ private void drawLeftRightSplit(Canvas canvas) { // Get the bounds where we will draw the background image int width = getBounds().width(); int height = getBounds().height(); // The left half of the background image, excluding center channel RectF leftSide = new RectF( 0, 0, (width / 2f) - (icon.getCenterChannelSize() / 2f), height ); // The right half of the background image, excluding center channel RectF rightSide = new RectF( (width / 2f) + (icon.getCenterChannelSize() / 2f), 0, width, height ); drawCustomRoundedRect(canvas, leftSide, new float[]{ icon.getBigRadius(), icon.getBigRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getBigRadius(), icon.getBigRadius()}); drawCustomRoundedRect(canvas, rightSide, new float[]{ icon.getSmallRadius(), icon.getSmallRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getSmallRadius(), icon.getSmallRadius()}); } /** * When device is in portrait, we draw the rectangles with a top-bottom split. */ private void drawTopBottomSplit(Canvas canvas) { // Get the bounds where we will draw the background image int width = getBounds().width(); int height = getBounds().height(); // The top half of the background image, excluding center channel RectF topSide = new RectF( 0, 0, width, (height / 2f) - (icon.getCenterChannelSize() / 2f) ); // The bottom half of the background image, excluding center channel RectF bottomSide = new RectF( 0, (height / 2f) + (icon.getCenterChannelSize() / 2f), width, height ); drawCustomRoundedRect(canvas, topSide, new float[]{ icon.getBigRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getSmallRadius()}); drawCustomRoundedRect(canvas, bottomSide, new float[]{ icon.getSmallRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getBigRadius()}); } /** * Draws a rectangle with custom rounded corners. * @param c The Canvas to draw on. * @param rect The bounds of the rectangle. * @param radii An array of 8 radii for the corners: top left x, top left y, top right x, top * right y, bottom right x, and so on. */ private void drawCustomRoundedRect(Canvas c, RectF rect, float[] radii) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Canvas.drawDoubleRoundRect is supported from Q onward c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, mBackgroundPaint); } else { // Fallback rectangle with uniform rounded corners c.drawRoundRect(rect, icon.getBigRadius(), icon.getBigRadius(), mBackgroundPaint); } } @Override public int getOpacity() { return PixelFormat.OPAQUE; } @Override public void setAlpha(int i) { mBackgroundPaint.setAlpha(i); } @Override public void setColorFilter(ColorFilter colorFilter) { // Required by Drawable but not used. } } src/com/android/launcher3/apppairs/AppPairIconDrawable.java 0 → 100644 +208 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.launcher3.apppairs; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Build; import androidx.annotation.NonNull; import com.android.launcher3.icons.FastBitmapDrawable; /** * A composed Drawable consisting of the two app pair icons and the background behind them (looks * like two rectangles). */ class AppPairIconDrawable extends Drawable { private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final AppPairIconDrawingParams mP; private final FastBitmapDrawable mIcon1; private final FastBitmapDrawable mIcon2; /** * Null values to use with * {@link Canvas#drawDoubleRoundRect(RectF, float[], RectF, float[], Paint)}, since there * doesn't seem to be any other API for drawing rectangles with 4 different corner radii. */ private static final RectF EMPTY_RECT = new RectF(); private static final float[] ARRAY_OF_ZEROES = new float[8]; AppPairIconDrawable( AppPairIconDrawingParams p, FastBitmapDrawable icon1, FastBitmapDrawable icon2) { mP = p; mBackgroundPaint.setStyle(Paint.Style.FILL); mBackgroundPaint.setColor(p.getBgColor()); mIcon1 = icon1; mIcon2 = icon2; } @Override public void draw(@NonNull Canvas canvas) { if (mP.isLeftRightSplit()) { drawLeftRightSplit(canvas); } else { drawTopBottomSplit(canvas); } canvas.translate( mP.getStandardIconPadding() + mP.getOuterPadding(), mP.getStandardIconPadding() + mP.getOuterPadding() ); // Draw first icon. canvas.save(); // The app icons are placed differently depending on device orientation. if (mP.isLeftRightSplit()) { canvas.translate( mP.getInnerPadding(), mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f ); } else { canvas.translate( mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f, mP.getInnerPadding() ); } mIcon1.draw(canvas); canvas.restore(); // Draw second icon. canvas.save(); // The app icons are placed differently depending on device orientation. if (mP.isLeftRightSplit()) { canvas.translate( mP.getBackgroundSize() - (mP.getInnerPadding() + mP.getMemberIconSize()), mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f ); } else { canvas.translate( mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f, mP.getBackgroundSize() - (mP.getInnerPadding() + mP.getMemberIconSize()) ); } mIcon2.draw(canvas); } /** * When device is in landscape, we draw the rectangles with a left-right split. */ private void drawLeftRightSplit(Canvas canvas) { // Get the bounds where we will draw the background image int width = mP.getIconSize(); int height = mP.getIconSize(); // The left half of the background image, excluding center channel RectF leftSide = new RectF( mP.getStandardIconPadding() + mP.getOuterPadding(), mP.getStandardIconPadding() + mP.getOuterPadding(), (width / 2f) - (mP.getCenterChannelSize() / 2f), height - (mP.getStandardIconPadding() + mP.getOuterPadding()) ); // The right half of the background image, excluding center channel RectF rightSide = new RectF( (width / 2f) + (mP.getCenterChannelSize() / 2f), (mP.getStandardIconPadding() + mP.getOuterPadding()), width - (mP.getStandardIconPadding() + mP.getOuterPadding()), height - (mP.getStandardIconPadding() + mP.getOuterPadding()) ); drawCustomRoundedRect(canvas, leftSide, new float[]{ mP.getBigRadius(), mP.getBigRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getBigRadius(), mP.getBigRadius()}); drawCustomRoundedRect(canvas, rightSide, new float[]{ mP.getSmallRadius(), mP.getSmallRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getSmallRadius(), mP.getSmallRadius()}); } /** * When device is in portrait, we draw the rectangles with a top-bottom split. */ private void drawTopBottomSplit(Canvas canvas) { // Get the bounds where we will draw the background image int width = mP.getIconSize(); int height = mP.getIconSize(); // The top half of the background image, excluding center channel RectF topSide = new RectF( (mP.getStandardIconPadding() + mP.getOuterPadding()), (mP.getStandardIconPadding() + mP.getOuterPadding()), width - (mP.getStandardIconPadding() + mP.getOuterPadding()), (height / 2f) - (mP.getCenterChannelSize() / 2f) ); // The bottom half of the background image, excluding center channel RectF bottomSide = new RectF( (mP.getStandardIconPadding() + mP.getOuterPadding()), (height / 2f) + (mP.getCenterChannelSize() / 2f), width - (mP.getStandardIconPadding() + mP.getOuterPadding()), height - (mP.getStandardIconPadding() + mP.getOuterPadding()) ); drawCustomRoundedRect(canvas, topSide, new float[]{ mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getSmallRadius()}); drawCustomRoundedRect(canvas, bottomSide, new float[]{ mP.getSmallRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius()}); } /** * Draws a rectangle with custom rounded corners. * @param c The Canvas to draw on. * @param rect The bounds of the rectangle. * @param radii An array of 8 radii for the corners: top left x, top left y, top right x, top * right y, bottom right x, and so on. */ private void drawCustomRoundedRect(Canvas c, RectF rect, float[] radii) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Canvas.drawDoubleRoundRect is supported from Q onward c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, mBackgroundPaint); } else { // Fallback rectangle with uniform rounded corners c.drawRoundRect(rect, mP.getBigRadius(), mP.getBigRadius(), mBackgroundPaint); } } @Override public int getOpacity() { return PixelFormat.OPAQUE; } @Override public void setAlpha(int i) { mBackgroundPaint.setAlpha(i); } @Override public void setColorFilter(ColorFilter colorFilter) { mBackgroundPaint.setColorFilter(colorFilter); } } Loading
quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +2 −1 Original line number Diff line number Diff line Loading @@ -354,7 +354,8 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar break; case ITEM_TYPE_APP_PAIR: hotseatView = AppPairIcon.inflateIcon( expectedLayoutResId, mActivityContext, this, folderInfo); expectedLayoutResId, mActivityContext, this, folderInfo, BubbleTextView.DISPLAY_TASKBAR); ((AppPairIcon) hotseatView).setTextVisible(false); break; default: Loading
src/com/android/launcher3/BubbleTextView.java +3 −4 Original line number Diff line number Diff line Loading @@ -58,7 +58,6 @@ import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; import com.android.launcher3.accessibility.BaseAccessibilityDelegate; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dot.DotInfo; import com.android.launcher3.dragndrop.DragOptions.PreDragCondition; import com.android.launcher3.dragndrop.DraggableView; Loading Loading @@ -96,10 +95,10 @@ import java.util.Locale; public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, IconLabelDotView, DraggableView, Reorderable { private static final int DISPLAY_WORKSPACE = 0; public static final int DISPLAY_WORKSPACE = 0; public static final int DISPLAY_ALL_APPS = 1; private static final int DISPLAY_FOLDER = 2; protected static final int DISPLAY_TASKBAR = 5; public static final int DISPLAY_FOLDER = 2; public static final int DISPLAY_TASKBAR = 5; public static final int DISPLAY_SEARCH_RESULT = 6; public static final int DISPLAY_SEARCH_RESULT_SMALL = 7; public static final int DISPLAY_PREDICTION_ROW = 8; Loading
src/com/android/launcher3/apppairs/AppPairIcon.java +48 −19 Original line number Diff line number Diff line Loading @@ -16,13 +16,14 @@ package com.android.launcher3.apppairs; import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; Loading @@ -38,7 +39,6 @@ import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.util.MultiTranslateDelegate; import com.android.launcher3.views.ActivityContext; import java.util.Collections; import java.util.Comparator; import java.util.function.Predicate; Loading @@ -62,6 +62,9 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab private BubbleTextView mAppPairName; // The underlying ItemInfo that stores info about the app pair members, etc. private FolderInfo mInfo; // The containing element that holds this icon: workspace, taskbar, folder, etc. Affects certain // aspects of how the icon is drawn. private int mContainer; // Required for Reorderable -- handles translation and bouncing movements private final MultiTranslateDelegate mTranslateDelegate = new MultiTranslateDelegate(this); Loading @@ -79,7 +82,7 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab * Builds an AppPairIcon to be added to the Launcher. */ public static AppPairIcon inflateIcon(int resId, ActivityContext activity, @Nullable ViewGroup group, FolderInfo appPairInfo) { @Nullable ViewGroup group, FolderInfo appPairInfo, int container) { DeviceProfile grid = activity.getDeviceProfile(); LayoutInflater inflater = (group != null) ? LayoutInflater.from(group.getContext()) Loading @@ -87,30 +90,32 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab AppPairIcon icon = (AppPairIcon) inflater.inflate(resId, group, false); // Sort contents, so that left-hand app comes first Collections.sort(appPairInfo.contents, Comparator.comparingInt(a -> a.rank)); appPairInfo.contents.sort(Comparator.comparingInt(a -> a.rank)); icon.setClipToPadding(false); icon.setTag(appPairInfo); icon.setOnClickListener(activity.getItemOnClickListener()); icon.mInfo = appPairInfo; icon.mContainer = container; if (icon.mInfo.contents.size() != 2) { Log.wtf(TAG, "AppPair contents not 2, size: " + icon.mInfo.contents.size()); return icon; } icon.checkScreenSize(); icon.checkDisabledState(); // Set up icon drawable area icon.mIconGraphic = icon.findViewById(R.id.app_pair_icon_graphic); icon.mIconGraphic.init(activity, icon); icon.mIconGraphic.init(icon, container); // Set up app pair title icon.mAppPairName = icon.findViewById(R.id.app_pair_icon_name); icon.mAppPairName.setCompoundDrawablePadding(0); FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mAppPairName.getLayoutParams(); lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx; // Shift the title text down to leave room for the icon graphic. Since the icon graphic is // a separate element (and not set as a CompoundDrawable on the BubbleTextView), we need to // shift the text down manually. lp.topMargin = container == DISPLAY_FOLDER ? grid.folderChildIconSizePx + grid.folderChildDrawablePaddingPx : grid.iconSizePx + grid.iconDrawablePaddingPx; // For some reason, app icons have setIncludeFontPadding(false) inside folders, so we set it // here to match that. icon.mAppPairName.setIncludeFontPadding(container != DISPLAY_FOLDER); icon.mAppPairName.setText(appPairInfo.title); // Set up accessibility Loading Loading @@ -174,7 +179,11 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab return mInfo; } public View getIconDrawableArea() { public BubbleTextView getTitleTextView() { return mAppPairName; } public AppPairIconGraphic getIconDrawableArea() { return mIconGraphic; } Loading @@ -194,12 +203,14 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab * {@link AppPairIconGraphic#dispatchDraw(Canvas)} or clicked on * {@link com.android.launcher3.touch.ItemClickHandler#onClickAppPairIcon(View)} */ public void checkScreenSize() { public void checkDisabledState() { DeviceProfile dp = ActivityContext.lookupContext(getContext()).getDeviceProfile(); // If user is on a small screen, we can't launch if either of the apps is non-resizeable mIsLaunchableAtScreenSize = dp.isTablet || getInfo().contents.stream().noneMatch( wii -> wii.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE)); // Invalidate to update icons mIconGraphic.redraw(); } /** Loading @@ -209,8 +220,26 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab // If either of the app pair icons return true on the predicate (i.e. in the list of // updated apps), redraw the icon graphic (icon background and both icons). if (getInfo().contents.stream().anyMatch(itemCheck)) { checkScreenSize(); mIconGraphic.invalidate(); checkDisabledState(); } } /** * Inside folders, icons are vertically centered in their rows. See * {@link BubbleTextView#onMeasure(int, int)} for comparison. */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mContainer == DISPLAY_FOLDER) { int height = MeasureSpec.getSize(heightMeasureSpec); ActivityContext activity = ActivityContext.lookupContext(getContext()); Paint.FontMetrics fm = mAppPairName.getPaint().getFontMetrics(); int cellHeightPx = activity.getDeviceProfile().folderChildIconSizePx + activity.getDeviceProfile().folderChildDrawablePaddingPx + (int) Math.ceil(fm.bottom - fm.top); setPadding(getPaddingLeft(), (height - cellHeightPx) / 2, getPaddingRight(), getPaddingBottom()); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }
src/com/android/launcher3/apppairs/AppPairIconBackground.java +0 −166 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.launcher3.apppairs; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Build; import com.android.launcher3.R; /** * A Drawable for the background behind the twin app icons (looks like two rectangles). */ class AppPairIconBackground extends Drawable { // The underlying view that we are drawing this background on. private final AppPairIconGraphic icon; private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); /** * Null values to use with * {@link Canvas#drawDoubleRoundRect(RectF, float[], RectF, float[], Paint)}, since there * doesn't seem to be any other API for drawing rectangles with 4 different corner radii. */ private static final RectF EMPTY_RECT = new RectF(); private static final float[] ARRAY_OF_ZEROES = new float[8]; AppPairIconBackground(Context context, AppPairIconGraphic iconGraphic) { icon = iconGraphic; // Set up background paint color TypedArray ta = context.getTheme().obtainStyledAttributes(R.styleable.FolderIconPreview); mBackgroundPaint.setStyle(Paint.Style.FILL); mBackgroundPaint.setColor( ta.getColor(R.styleable.FolderIconPreview_folderPreviewColor, 0)); ta.recycle(); } @Override public void draw(Canvas canvas) { if (icon.isLeftRightSplit()) { drawLeftRightSplit(canvas); } else { drawTopBottomSplit(canvas); } } /** * When device is in landscape, we draw the rectangles with a left-right split. */ private void drawLeftRightSplit(Canvas canvas) { // Get the bounds where we will draw the background image int width = getBounds().width(); int height = getBounds().height(); // The left half of the background image, excluding center channel RectF leftSide = new RectF( 0, 0, (width / 2f) - (icon.getCenterChannelSize() / 2f), height ); // The right half of the background image, excluding center channel RectF rightSide = new RectF( (width / 2f) + (icon.getCenterChannelSize() / 2f), 0, width, height ); drawCustomRoundedRect(canvas, leftSide, new float[]{ icon.getBigRadius(), icon.getBigRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getBigRadius(), icon.getBigRadius()}); drawCustomRoundedRect(canvas, rightSide, new float[]{ icon.getSmallRadius(), icon.getSmallRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getSmallRadius(), icon.getSmallRadius()}); } /** * When device is in portrait, we draw the rectangles with a top-bottom split. */ private void drawTopBottomSplit(Canvas canvas) { // Get the bounds where we will draw the background image int width = getBounds().width(); int height = getBounds().height(); // The top half of the background image, excluding center channel RectF topSide = new RectF( 0, 0, width, (height / 2f) - (icon.getCenterChannelSize() / 2f) ); // The bottom half of the background image, excluding center channel RectF bottomSide = new RectF( 0, (height / 2f) + (icon.getCenterChannelSize() / 2f), width, height ); drawCustomRoundedRect(canvas, topSide, new float[]{ icon.getBigRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getSmallRadius()}); drawCustomRoundedRect(canvas, bottomSide, new float[]{ icon.getSmallRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getSmallRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getBigRadius(), icon.getBigRadius()}); } /** * Draws a rectangle with custom rounded corners. * @param c The Canvas to draw on. * @param rect The bounds of the rectangle. * @param radii An array of 8 radii for the corners: top left x, top left y, top right x, top * right y, bottom right x, and so on. */ private void drawCustomRoundedRect(Canvas c, RectF rect, float[] radii) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Canvas.drawDoubleRoundRect is supported from Q onward c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, mBackgroundPaint); } else { // Fallback rectangle with uniform rounded corners c.drawRoundRect(rect, icon.getBigRadius(), icon.getBigRadius(), mBackgroundPaint); } } @Override public int getOpacity() { return PixelFormat.OPAQUE; } @Override public void setAlpha(int i) { mBackgroundPaint.setAlpha(i); } @Override public void setColorFilter(ColorFilter colorFilter) { // Required by Drawable but not used. } }
src/com/android/launcher3/apppairs/AppPairIconDrawable.java 0 → 100644 +208 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.launcher3.apppairs; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Build; import androidx.annotation.NonNull; import com.android.launcher3.icons.FastBitmapDrawable; /** * A composed Drawable consisting of the two app pair icons and the background behind them (looks * like two rectangles). */ class AppPairIconDrawable extends Drawable { private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final AppPairIconDrawingParams mP; private final FastBitmapDrawable mIcon1; private final FastBitmapDrawable mIcon2; /** * Null values to use with * {@link Canvas#drawDoubleRoundRect(RectF, float[], RectF, float[], Paint)}, since there * doesn't seem to be any other API for drawing rectangles with 4 different corner radii. */ private static final RectF EMPTY_RECT = new RectF(); private static final float[] ARRAY_OF_ZEROES = new float[8]; AppPairIconDrawable( AppPairIconDrawingParams p, FastBitmapDrawable icon1, FastBitmapDrawable icon2) { mP = p; mBackgroundPaint.setStyle(Paint.Style.FILL); mBackgroundPaint.setColor(p.getBgColor()); mIcon1 = icon1; mIcon2 = icon2; } @Override public void draw(@NonNull Canvas canvas) { if (mP.isLeftRightSplit()) { drawLeftRightSplit(canvas); } else { drawTopBottomSplit(canvas); } canvas.translate( mP.getStandardIconPadding() + mP.getOuterPadding(), mP.getStandardIconPadding() + mP.getOuterPadding() ); // Draw first icon. canvas.save(); // The app icons are placed differently depending on device orientation. if (mP.isLeftRightSplit()) { canvas.translate( mP.getInnerPadding(), mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f ); } else { canvas.translate( mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f, mP.getInnerPadding() ); } mIcon1.draw(canvas); canvas.restore(); // Draw second icon. canvas.save(); // The app icons are placed differently depending on device orientation. if (mP.isLeftRightSplit()) { canvas.translate( mP.getBackgroundSize() - (mP.getInnerPadding() + mP.getMemberIconSize()), mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f ); } else { canvas.translate( mP.getBackgroundSize() / 2f - mP.getMemberIconSize() / 2f, mP.getBackgroundSize() - (mP.getInnerPadding() + mP.getMemberIconSize()) ); } mIcon2.draw(canvas); } /** * When device is in landscape, we draw the rectangles with a left-right split. */ private void drawLeftRightSplit(Canvas canvas) { // Get the bounds where we will draw the background image int width = mP.getIconSize(); int height = mP.getIconSize(); // The left half of the background image, excluding center channel RectF leftSide = new RectF( mP.getStandardIconPadding() + mP.getOuterPadding(), mP.getStandardIconPadding() + mP.getOuterPadding(), (width / 2f) - (mP.getCenterChannelSize() / 2f), height - (mP.getStandardIconPadding() + mP.getOuterPadding()) ); // The right half of the background image, excluding center channel RectF rightSide = new RectF( (width / 2f) + (mP.getCenterChannelSize() / 2f), (mP.getStandardIconPadding() + mP.getOuterPadding()), width - (mP.getStandardIconPadding() + mP.getOuterPadding()), height - (mP.getStandardIconPadding() + mP.getOuterPadding()) ); drawCustomRoundedRect(canvas, leftSide, new float[]{ mP.getBigRadius(), mP.getBigRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getBigRadius(), mP.getBigRadius()}); drawCustomRoundedRect(canvas, rightSide, new float[]{ mP.getSmallRadius(), mP.getSmallRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getSmallRadius(), mP.getSmallRadius()}); } /** * When device is in portrait, we draw the rectangles with a top-bottom split. */ private void drawTopBottomSplit(Canvas canvas) { // Get the bounds where we will draw the background image int width = mP.getIconSize(); int height = mP.getIconSize(); // The top half of the background image, excluding center channel RectF topSide = new RectF( (mP.getStandardIconPadding() + mP.getOuterPadding()), (mP.getStandardIconPadding() + mP.getOuterPadding()), width - (mP.getStandardIconPadding() + mP.getOuterPadding()), (height / 2f) - (mP.getCenterChannelSize() / 2f) ); // The bottom half of the background image, excluding center channel RectF bottomSide = new RectF( (mP.getStandardIconPadding() + mP.getOuterPadding()), (height / 2f) + (mP.getCenterChannelSize() / 2f), width - (mP.getStandardIconPadding() + mP.getOuterPadding()), height - (mP.getStandardIconPadding() + mP.getOuterPadding()) ); drawCustomRoundedRect(canvas, topSide, new float[]{ mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getSmallRadius()}); drawCustomRoundedRect(canvas, bottomSide, new float[]{ mP.getSmallRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getSmallRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius(), mP.getBigRadius()}); } /** * Draws a rectangle with custom rounded corners. * @param c The Canvas to draw on. * @param rect The bounds of the rectangle. * @param radii An array of 8 radii for the corners: top left x, top left y, top right x, top * right y, bottom right x, and so on. */ private void drawCustomRoundedRect(Canvas c, RectF rect, float[] radii) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Canvas.drawDoubleRoundRect is supported from Q onward c.drawDoubleRoundRect(rect, radii, EMPTY_RECT, ARRAY_OF_ZEROES, mBackgroundPaint); } else { // Fallback rectangle with uniform rounded corners c.drawRoundRect(rect, mP.getBigRadius(), mP.getBigRadius(), mBackgroundPaint); } } @Override public int getOpacity() { return PixelFormat.OPAQUE; } @Override public void setAlpha(int i) { mBackgroundPaint.setAlpha(i); } @Override public void setColorFilter(ColorFilter colorFilter) { mBackgroundPaint.setColorFilter(colorFilter); } }