/* * Copyright (C) 2017 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.dragndrop; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import android.annotation.TargetApi; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Path; import android.graphics.Point; import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.util.Log; import androidx.annotation.Nullable; import com.android.launcher3.Launcher; import com.android.launcher3.R; 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; /** * {@link AdaptiveIconDrawable} representation of a {@link FolderIcon} */ @TargetApi(Build.VERSION_CODES.O) public class FolderAdaptiveIcon extends AdaptiveIconDrawable { private static final String TAG = "FolderAdaptiveIcon"; private final Drawable mBadge; private final Path mMask; private final ConstantState mConstantState; private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) { super(bg, fg); mBadge = badge; mMask = mask; mConstantState = new MyConstantState(bg.getConstantState(), fg.getConstantState(), badge.getConstantState(), mask); } @Override public Path getIconMask() { return mMask; } public Drawable getBadge() { return mBadge; } public static @Nullable FolderAdaptiveIcon createFolderAdaptiveIcon( Launcher launcher, int folderId, Point dragViewSize) { Preconditions.assertNonUiThread(); int margin = launcher.getResources() .getDimensionPixelSize(R.dimen.blur_size_medium_outline); // Allocate various bitmaps on the background thread, because why not! int width = dragViewSize.x - margin; int height = dragViewSize.y - margin; if (width <= 0 || height <= 0) { return null; } final Bitmap badge = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); // Create the actual drawable on the UI thread to avoid race conditions with // FolderIcon draw pass try { return MAIN_EXECUTOR.submit(() -> { FolderIcon icon = launcher.findFolderIcon(folderId); return icon == null ? null : createDrawableOnUiThread(icon, badge, dragViewSize); }).get(); } catch (Exception e) { Log.e(TAG, "Unable to create folder icon", e); return null; } } /** * Initializes various bitmaps on the UI thread and returns the final drawable. */ private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon, Bitmap badgeBitmap, Point dragViewSize) { Preconditions.assertUIThread(); float margin = icon.getResources().getDimension(R.dimen.blur_size_medium_outline) / 2; Canvas c = new Canvas(); PreviewBackground bg = icon.getFolderBackground(); // Initialize badge c.setBitmap(badgeBitmap); bg.drawShadow(c); bg.drawBackgroundStroke(c); icon.drawDot(c); // Initialize preview final float sizeScaleFactor = 1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction(); final int previewWidth = (int) (dragViewSize.x * sizeScaleFactor); final int previewHeight = (int) (dragViewSize.y * sizeScaleFactor); final float shiftFactor = AdaptiveIconDrawable.getExtraInsetFraction() / sizeScaleFactor; final float previewShiftX = shiftFactor * previewWidth; final float previewShiftY = shiftFactor * previewHeight; Bitmap previewBitmap = BitmapRenderer.createHardwareBitmap(previewWidth, previewHeight, (canvas) -> { int count = canvas.save(); canvas.translate(previewShiftX, previewShiftY); icon.getPreviewItemManager().draw(canvas); canvas.restoreToCount(count); }); // Initialize mask Path mask = new Path(); Matrix m = new Matrix(); m.setTranslate(margin, margin); bg.getClipPath().transform(m, mask); ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBitmap, margin, margin); ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, margin - previewShiftX, margin - previewShiftY); return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask); } @Override public ConstantState getConstantState() { return mConstantState; } private static class MyConstantState extends ConstantState { private final ConstantState mBg; private final ConstantState mFg; private final ConstantState mBadge; private final Path mMask; MyConstantState(ConstantState bg, ConstantState fg, ConstantState badge, Path mask) { mBg = bg; mFg = fg; mBadge = badge; mMask = mask; } @Override public Drawable newDrawable() { return new FolderAdaptiveIcon(mBg.newDrawable(), mFg.newDrawable(), mBadge.newDrawable(), mMask); } @Override public int getChangingConfigurations() { return mBg.getChangingConfigurations() & mFg.getChangingConfigurations() & mBadge.getChangingConfigurations(); } } }