1/* 2 * Copyright (C) 2017 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 17package com.android.launcher3.dragndrop; 18 19import android.annotation.TargetApi; 20import android.graphics.Bitmap; 21import android.graphics.Canvas; 22import android.graphics.ColorFilter; 23import android.graphics.Matrix; 24import android.graphics.Paint; 25import android.graphics.Path; 26import android.graphics.PixelFormat; 27import android.graphics.Point; 28import android.graphics.drawable.AdaptiveIconDrawable; 29import android.graphics.drawable.ColorDrawable; 30import android.graphics.drawable.Drawable; 31import android.os.Build; 32import android.util.Log; 33 34import com.android.launcher3.Launcher; 35import com.android.launcher3.MainThreadExecutor; 36import com.android.launcher3.R; 37import com.android.launcher3.folder.FolderIcon; 38import com.android.launcher3.folder.PreviewBackground; 39import com.android.launcher3.util.Preconditions; 40 41import java.util.concurrent.Callable; 42 43/** 44 * {@link AdaptiveIconDrawable} representation of a {@link FolderIcon} 45 */ 46@TargetApi(Build.VERSION_CODES.O) 47public class FolderAdaptiveIcon extends AdaptiveIconDrawable { 48 private static final String TAG = "FolderAdaptiveIcon"; 49 50 private final Drawable mBadge; 51 private final Path mMask; 52 53 private FolderAdaptiveIcon(Drawable bg, Drawable fg, Drawable badge, Path mask) { 54 super(bg, fg); 55 mBadge = badge; 56 mMask = mask; 57 } 58 59 @Override 60 public Path getIconMask() { 61 return mMask; 62 } 63 64 public Drawable getBadge() { 65 return mBadge; 66 } 67 68 public static FolderAdaptiveIcon createFolderAdaptiveIcon( 69 final Launcher launcher, final long folderId, Point dragViewSize) { 70 Preconditions.assertNonUiThread(); 71 int margin = launcher.getResources() 72 .getDimensionPixelSize(R.dimen.blur_size_medium_outline); 73 74 // Allocate various bitmaps on the background thread, because why not! 75 final Bitmap badge = Bitmap.createBitmap( 76 dragViewSize.x - margin, dragViewSize.y - margin, Bitmap.Config.ARGB_8888); 77 78 // The bitmap for the preview is generated larger than needed to allow for the spring effect 79 float sizeScaleFactor = 1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction(); 80 final Bitmap preview = Bitmap.createBitmap( 81 (int) (dragViewSize.x * sizeScaleFactor), (int) (dragViewSize.y * sizeScaleFactor), 82 Bitmap.Config.ARGB_8888); 83 84 // Create the actual drawable on the UI thread to avoid race conditions with 85 // FolderIcon draw pass 86 try { 87 return new MainThreadExecutor().submit(new Callable<FolderAdaptiveIcon>() { 88 @Override 89 public FolderAdaptiveIcon call() throws Exception { 90 FolderIcon icon = launcher.findFolderIcon(folderId); 91 return icon == null ? null : createDrawableOnUiThread(icon, badge, preview); 92 } 93 }).get(); 94 } catch (Exception e) { 95 Log.e(TAG, "Unable to create folder icon", e); 96 return null; 97 } 98 } 99 100 /** 101 * Initializes various bitmaps on the UI thread and returns the final drawable. 102 */ 103 private static FolderAdaptiveIcon createDrawableOnUiThread(FolderIcon icon, 104 Bitmap badgeBitmap, Bitmap previewBitmap) { 105 Preconditions.assertUIThread(); 106 float margin = icon.getResources().getDimension(R.dimen.blur_size_medium_outline) / 2; 107 108 Canvas c = new Canvas(); 109 PreviewBackground bg = icon.getFolderBackground(); 110 111 // Initialize badge 112 c.setBitmap(badgeBitmap); 113 bg.drawShadow(c); 114 bg.drawBackgroundStroke(c); 115 icon.drawBadge(c); 116 117 // Initialize preview 118 float shiftFactor = AdaptiveIconDrawable.getExtraInsetFraction() / 119 (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction()); 120 float previewShiftX = shiftFactor * previewBitmap.getWidth(); 121 float previewShiftY = shiftFactor * previewBitmap.getHeight(); 122 123 c.setBitmap(previewBitmap); 124 c.translate(previewShiftX, previewShiftY); 125 icon.getPreviewItemManager().draw(c); 126 c.setBitmap(null); 127 128 // Initialize mask 129 Path mask = new Path(); 130 Matrix m = new Matrix(); 131 m.setTranslate(margin, margin); 132 bg.getClipPath().transform(m, mask); 133 134 ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBitmap, margin, margin); 135 ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 136 margin - previewShiftX, margin - previewShiftY); 137 138 return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask); 139 } 140 141 /** 142 * A simple drawable which draws a bitmap at a fixed position irrespective of the bounds 143 */ 144 private static class ShiftedBitmapDrawable extends Drawable { 145 146 private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 147 private final Bitmap mBitmap; 148 private final float mShiftX; 149 private final float mShiftY; 150 151 ShiftedBitmapDrawable(Bitmap bitmap, float shiftX, float shiftY) { 152 mBitmap = bitmap; 153 mShiftX = shiftX; 154 mShiftY = shiftY; 155 } 156 157 @Override 158 public void draw(Canvas canvas) { 159 canvas.drawBitmap(mBitmap, mShiftX, mShiftY, mPaint); 160 } 161 162 @Override 163 public void setAlpha(int i) { } 164 165 @Override 166 public void setColorFilter(ColorFilter colorFilter) { 167 mPaint.setColorFilter(colorFilter); 168 } 169 170 @Override 171 public int getOpacity() { 172 return PixelFormat.TRANSLUCENT; 173 } 174 } 175} 176