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