1 /*
2  * Copyright (C) 2016 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 
17 package com.android.launcher3.graphics;
18 
19 import static com.android.launcher3.BubbleTextView.DISPLAY_SEARCH_RESULT_APP_ROW;
20 
21 import android.content.Context;
22 import android.graphics.Bitmap;
23 import android.graphics.Canvas;
24 import android.graphics.Rect;
25 import android.graphics.drawable.Drawable;
26 import android.graphics.drawable.InsetDrawable;
27 import android.view.View;
28 
29 import androidx.annotation.Nullable;
30 
31 import com.android.launcher3.BubbleTextView;
32 import com.android.launcher3.R;
33 import com.android.launcher3.dragndrop.DraggableView;
34 import com.android.launcher3.icons.BitmapRenderer;
35 import com.android.launcher3.icons.FastBitmapDrawable;
36 import com.android.launcher3.util.SafeCloseable;
37 import com.android.launcher3.views.ActivityContext;
38 import com.android.launcher3.widget.LauncherAppWidgetHostView;
39 
40 /**
41  * A utility class to generate preview bitmap for dragging.
42  */
43 public class DragPreviewProvider {
44     private final Rect mTempRect = new Rect();
45 
46     protected final View mView;
47 
48     // The padding added to the drag view during the preview generation.
49     public final int previewPadding;
50 
51     public final int blurSizeOutline;
52 
DragPreviewProvider(View view)53     public DragPreviewProvider(View view) {
54         this(view, view.getContext());
55     }
56 
DragPreviewProvider(View view, Context context)57     public DragPreviewProvider(View view, Context context) {
58         mView = view;
59         blurSizeOutline =
60                 context.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline);
61         previewPadding = blurSizeOutline;
62     }
63 
64     /**
65      * Draws the {@link #mView} into the given {@param destCanvas}.
66      */
drawDragView(Canvas destCanvas, float scale)67     protected void drawDragView(Canvas destCanvas, float scale) {
68         int saveCount = destCanvas.save();
69         destCanvas.scale(scale, scale);
70 
71         if (mView instanceof DraggableView) {
72             DraggableView dv = (DraggableView) mView;
73             try (SafeCloseable t = dv.prepareDrawDragView()) {
74                 dv.getSourceVisualDragBounds(mTempRect);
75                 destCanvas.translate(blurSizeOutline / 2 - mTempRect.left,
76                         blurSizeOutline / 2 - mTempRect.top);
77                 mView.draw(destCanvas);
78             }
79         }
80         destCanvas.restoreToCount(saveCount);
81     }
82 
83     /**
84      * Returns a new drawable to show when the {@link #mView} is being dragged around.
85      * Responsibility for the drawable is transferred to the caller.
86      */
createDrawable()87     public Drawable createDrawable() {
88         if (mView instanceof LauncherAppWidgetHostView) {
89             return null;
90         }
91 
92         int width = 0;
93         int height = 0;
94         // Assume scaleX == scaleY, which is always the case for workspace items.
95         float scale = mView.getScaleX();
96         if (mView instanceof DraggableView) {
97             ((DraggableView) mView).getSourceVisualDragBounds(mTempRect);
98             width = mTempRect.width();
99             height = mTempRect.height();
100         } else {
101             width = mView.getWidth();
102             height = mView.getHeight();
103         }
104 
105         if (mView instanceof BubbleTextView btv
106                 && btv.getIconDisplay() == DISPLAY_SEARCH_RESULT_APP_ROW) {
107             FastBitmapDrawable icon = ((BubbleTextView) mView).getIcon();
108             Drawable drawable = icon.getConstantState().newDrawable();
109             float xInset = (float) blurSizeOutline / (float) (width + blurSizeOutline);
110             float yInset = (float) blurSizeOutline / (float) (height + blurSizeOutline);
111             return new InsetDrawable(drawable, xInset / 2, yInset / 2, xInset / 2, yInset / 2);
112         }
113 
114         return new FastBitmapDrawable(
115                 BitmapRenderer.createHardwareBitmap(width + blurSizeOutline,
116                         height + blurSizeOutline, (c) -> drawDragView(c, scale)));
117     }
118 
119     /**
120      * Returns the content view if the content should be rendered directly in
121      * {@link com.android.launcher3.dragndrop.DragView}. Otherwise, returns null.
122      */
123     @Nullable
getContentView()124     public View getContentView() {
125         if (mView instanceof LauncherAppWidgetHostView) {
126             return mView;
127         }
128         return null;
129     }
130 
getDrawableBounds(Drawable d)131     protected static Rect getDrawableBounds(Drawable d) {
132         Rect bounds = new Rect();
133         d.copyBounds(bounds);
134         if (bounds.width() == 0 || bounds.height() == 0) {
135             bounds.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
136         } else {
137             bounds.offsetTo(0, 0);
138         }
139         return bounds;
140     }
141 
getScaleAndPosition(Drawable preview, int[] outPos)142     public float getScaleAndPosition(Drawable preview, int[] outPos) {
143         float scale = ActivityContext.lookupContext(mView.getContext())
144                 .getDragLayer().getLocationInDragLayer(mView, outPos);
145         if (mView instanceof LauncherAppWidgetHostView) {
146             // App widgets are technically scaled, but are drawn at their expected size -- so the
147             // app widget scale should not affect the scale of the preview.
148             scale /= ((LauncherAppWidgetHostView) mView).getScaleToFit();
149         }
150 
151         outPos[0] = Math.round(outPos[0] -
152                 (preview.getIntrinsicWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
153         outPos[1] = Math.round(outPos[1] - (1 - scale) * preview.getIntrinsicHeight() / 2
154                 - previewPadding / 2);
155         return scale;
156     }
157 
158     /** Returns the scale and position of a given view for drag-n-drop. */
getScaleAndPosition(View view, int[] outPos)159     public float getScaleAndPosition(View view, int[] outPos) {
160         float scale = ActivityContext.lookupContext(mView.getContext())
161                 .getDragLayer().getLocationInDragLayer(mView, outPos);
162         if (mView instanceof LauncherAppWidgetHostView) {
163             // App widgets are technically scaled, but are drawn at their expected size -- so the
164             // app widget scale should not affect the scale of the preview.
165             scale /= ((LauncherAppWidgetHostView) mView).getScaleToFit();
166         }
167 
168         outPos[0] = Math.round(outPos[0]
169                 - (view.getWidth() - scale * mView.getWidth() * mView.getScaleX()) / 2);
170         outPos[1] = Math.round(outPos[1] - (1 - scale) * view.getHeight() / 2 - previewPadding / 2);
171         return scale;
172     }
173 
convertPreviewToAlphaBitmap(Bitmap preview)174     protected Bitmap convertPreviewToAlphaBitmap(Bitmap preview) {
175         return preview.copy(Bitmap.Config.ALPHA_8, true);
176     }
177 }
178