Merge "Improving folder icon drawable" into tm-dev am: 2248d74666

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/17664075

Change-Id: I924952cab4ae79ec45c3f021d9ca0ba4f5239c36
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Sunny Goyal
2022-04-14 23:41:10 +00:00
committed by Automerger Merge Worker
3 changed files with 131 additions and 160 deletions
@@ -20,9 +20,13 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.Picture;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
@@ -31,10 +35,11 @@ import android.os.Build;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.Utilities;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.graphics.ShiftedBitmapDrawable;
import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.views.ActivityContext;
@@ -69,79 +74,104 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable {
return mBadge;
}
@TargetApi(Build.VERSION_CODES.P)
public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon(
ActivityContext activity, int folderId, Point dragViewSize) {
ActivityContext activity, int folderId, Point size) {
Preconditions.assertNonUiThread();
if (!Utilities.ATLEAST_P) {
return null;
}
// Create the actual drawable on the UI thread to avoid race conditions with
// assume square
if (size.x != size.y) {
return null;
}
int requestedSize = size.x;
// Only use the size actually needed for drawing the folder icon
int drawingSize = activity.getDeviceProfile().folderIconSizePx;
int foregroundSize = Math.max(requestedSize, drawingSize);
float shift = foregroundSize - requestedSize;
Picture background = new Picture();
Picture foreground = new Picture();
Picture badge = new Picture();
Canvas bgCanvas = background.beginRecording(requestedSize, requestedSize);
Canvas badgeCanvas = badge.beginRecording(requestedSize, requestedSize);
Canvas fgCanvas = foreground.beginRecording(foregroundSize, foregroundSize);
fgCanvas.translate(shift, shift);
// Do not clip the folder drawing since the icon previews extend outside the background.
Path mask = new Path();
mask.addRect(-shift, -shift, requestedSize + shift, requestedSize + shift,
Direction.CCW);
// Initialize the actual draw commands on the UI thread to avoid race conditions with
// FolderIcon draw pass
try {
return MAIN_EXECUTOR.submit(() -> {
MAIN_EXECUTOR.submit(() -> {
FolderIcon icon = activity.findFolderIcon(folderId);
return icon == null ? null : createDrawableOnUiThread(icon, dragViewSize);
if (icon == null) {
throw new IllegalArgumentException("Folder not found with id: " + folderId);
}
initLayersOnUiThread(icon, requestedSize, bgCanvas, fgCanvas, badgeCanvas);
}).get();
} catch (Exception e) {
Log.e(TAG, "Unable to create folder icon", e);
return null;
} finally {
background.endRecording();
foreground.endRecording();
badge.endRecording();
}
// Only convert foreground to a bitmap as it can contain multiple draw commands. Other
// layers either draw a nothing or a single draw call.
Bitmap fgBitmap = Bitmap.createBitmap(foreground);
Paint foregroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
// Do not use PictureDrawable as it moves the picture to the canvas bounds, whereas we want
// to draw it at (0,0)
return new FolderAdaptiveIcon(
new BitmapRendererDrawable(c -> c.drawPicture(background)),
new BitmapRendererDrawable(
c -> c.drawBitmap(fgBitmap, -shift, -shift, foregroundPaint)),
new BitmapRendererDrawable(c -> c.drawPicture(badge)),
mask);
}
private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon,
Point dragViewSize) {
Preconditions.assertUIThread();
@UiThread
private static void initLayersOnUiThread(FolderIcon icon, int size,
Canvas backgroundCanvas, Canvas foregroundCanvas, Canvas badgeCanvas) {
icon.getPreviewBounds(sTmpRect);
PreviewBackground bg = icon.getFolderBackground();
// assume square
assert (dragViewSize.x == dragViewSize.y);
final int previewSize = sTmpRect.width();
final int margin = (dragViewSize.x - previewSize) / 2;
PreviewBackground bg = icon.getFolderBackground();
final int margin = (size - previewSize) / 2;
final float previewShiftX = -sTmpRect.left + margin;
final float previewShiftY = -sTmpRect.top + margin;
// Initialize badge, which consists of the outline stroke, shadow and dot; these
// must be rendered above the foreground
Bitmap badgeBmp = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
(canvas) -> {
canvas.save();
canvas.translate(previewShiftX, previewShiftY);
bg.drawShadow(canvas);
bg.drawBackgroundStroke(canvas);
icon.drawDot(canvas);
canvas.restore();
});
badgeCanvas.save();
badgeCanvas.translate(previewShiftX, previewShiftY);
icon.drawDot(badgeCanvas);
badgeCanvas.restore();
// Initialize mask
Path mask = new Path();
Matrix m = new Matrix();
m.setTranslate(previewShiftX, previewShiftY);
bg.getClipPath().transform(m, mask);
// Draw foreground
foregroundCanvas.save();
foregroundCanvas.translate(previewShiftX, previewShiftY);
icon.getPreviewItemManager().draw(foregroundCanvas);
foregroundCanvas.restore();
Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
(canvas) -> {
canvas.save();
canvas.translate(previewShiftX, previewShiftY);
icon.getPreviewItemManager().draw(canvas);
canvas.restore();
});
Bitmap bgBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
(canvas) -> {
Paint p = new Paint();
p.setColor(bg.getBgColor());
canvas.drawCircle(dragViewSize.x / 2f, dragViewSize.y / 2f, bg.getRadius(), p);
});
ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBmp, 0, 0);
ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 0, 0);
ShiftedBitmapDrawable background = new ShiftedBitmapDrawable(bgBitmap, 0, 0);
return new FolderAdaptiveIcon(background, foreground, badge, mask);
// Draw background
Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
backgroundPaint.setColor(bg.getBgColor());
bg.drawShadow(backgroundCanvas);
backgroundCanvas.drawCircle(size / 2f, size / 2f, bg.getRadius(), backgroundPaint);
bg.drawBackgroundStroke(backgroundCanvas);
}
@Override
@@ -174,4 +204,52 @@ public class FolderAdaptiveIcon extends AdaptiveIconDrawable {
& mBadge.getChangingConfigurations();
}
}
private static class BitmapRendererDrawable extends Drawable {
private final BitmapRenderer mRenderer;
BitmapRendererDrawable(BitmapRenderer renderer) {
mRenderer = renderer;
}
@Override
public void draw(Canvas canvas) {
mRenderer.draw(canvas);
}
@Override
public void setAlpha(int i) { }
@Override
public void setColorFilter(ColorFilter colorFilter) { }
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public ConstantState getConstantState() {
return new MyConstantState(mRenderer);
}
private static class MyConstantState extends ConstantState {
private final BitmapRenderer mRenderer;
MyConstantState(BitmapRenderer renderer) {
mRenderer = renderer;
}
@Override
public Drawable newDrawable() {
return new BitmapRendererDrawable(mRenderer);
}
@Override
public int getChangingConfigurations() {
return 0;
}
}
}
}
@@ -1,105 +0,0 @@
/*
* Copyright (C) 2019 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.graphics;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
/**
* A simple drawable which draws a bitmap at a fixed position irrespective of the bounds
*/
public class ShiftedBitmapDrawable extends Drawable {
private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);
private final Bitmap mBitmap;
private float mShiftX;
private float mShiftY;
private final ConstantState mConstantState;
public ShiftedBitmapDrawable(Bitmap bitmap, float shiftX, float shiftY) {
mBitmap = bitmap;
mShiftX = shiftX;
mShiftY = shiftY;
mConstantState = new MyConstantState(mBitmap, mShiftX, mShiftY);
}
public float getShiftX() {
return mShiftX;
}
public float getShiftY() {
return mShiftY;
}
public void setShiftX(float shiftX) {
mShiftX = shiftX;
}
public void setShiftY(float shiftY) {
mShiftY = shiftY;
}
@Override
public void draw(Canvas canvas) {
canvas.drawBitmap(mBitmap, mShiftX, mShiftY, mPaint);
}
@Override
public void setAlpha(int i) { }
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public ConstantState getConstantState() {
return mConstantState;
}
private static class MyConstantState extends ConstantState {
private final Bitmap mBitmap;
private float mShiftX;
private float mShiftY;
MyConstantState(Bitmap bitmap, float shiftX, float shiftY) {
mBitmap = bitmap;
mShiftX = shiftX;
mShiftY = shiftY;
}
@Override
public Drawable newDrawable() {
return new ShiftedBitmapDrawable(mBitmap, mShiftX, mShiftY);
}
@Override
public int getChangingConfigurations() {
return 0;
}
}
}
@@ -49,7 +49,6 @@ import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.FastBitmapDrawable;
@@ -413,8 +412,7 @@ public class FloatingIconView extends FrameLayout implements
@WorkerThread
@SuppressWarnings("WrongThread")
private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) {
if (!(drawable instanceof AdaptiveIconDrawable)
|| (drawable instanceof FolderAdaptiveIcon)) {
if (!(drawable instanceof AdaptiveIconDrawable)) {
return 0;
}
int blurSizeOutline =