1 /*
2  * Copyright (C) 2008 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;
18 
19 import static android.view.MotionEvent.ACTION_DOWN;
20 
21 import static com.android.launcher3.CellLayout.FOLDER;
22 import static com.android.launcher3.CellLayout.HOTSEAT;
23 import static com.android.launcher3.CellLayout.WORKSPACE;
24 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_BUBBLE_ADJUSTMENT_ANIM;
25 import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_WIDGET_CENTERING;
26 
27 import android.app.WallpaperManager;
28 import android.content.Context;
29 import android.graphics.Point;
30 import android.graphics.PointF;
31 import android.graphics.Rect;
32 import android.os.Trace;
33 import android.view.MotionEvent;
34 import android.view.View;
35 import android.view.ViewGroup;
36 
37 import androidx.annotation.Nullable;
38 
39 import com.android.launcher3.CellLayout.ContainerType;
40 import com.android.launcher3.celllayout.CellLayoutLayoutParams;
41 import com.android.launcher3.folder.FolderIcon;
42 import com.android.launcher3.model.data.ItemInfo;
43 import com.android.launcher3.views.ActivityContext;
44 import com.android.launcher3.widget.LauncherAppWidgetHostView;
45 import com.android.launcher3.widget.NavigableAppWidgetHostView;
46 
47 public class ShortcutAndWidgetContainer extends ViewGroup implements FolderIcon.FolderIconParent {
48     static final String TAG = "ShortcutAndWidgetContainer";
49 
50     // These are temporary variables to prevent having to allocate a new object just to
51     // return an (x, y) value from helper functions. Do NOT use them to maintain other state.
52     private final int[] mTmpCellXY = new int[2];
53 
54     @ContainerType
55     private final int mContainerType;
56     private final WallpaperManager mWallpaperManager;
57 
58     private int mCellWidth;
59     private int mCellHeight;
60     private Point mBorderSpace;
61 
62     private int mCountX;
63     private int mCountY;
64 
65     private final ActivityContext mActivity;
66     private boolean mInvertIfRtl = false;
67 
68     @Nullable
69     private TranslationProvider mTranslationProvider = null;
70 
ShortcutAndWidgetContainer(Context context, @ContainerType int containerType)71     public ShortcutAndWidgetContainer(Context context, @ContainerType int containerType) {
72         super(context);
73         mActivity = ActivityContext.lookupContext(context);
74         mWallpaperManager = WallpaperManager.getInstance(context);
75         mContainerType = containerType;
76         setClipChildren(false);
77     }
78 
setCellDimensions(int cellWidth, int cellHeight, int countX, int countY, Point borderSpace)79     public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY,
80             Point borderSpace) {
81         mCellWidth = cellWidth;
82         mCellHeight = cellHeight;
83         mCountX = countX;
84         mCountY = countY;
85         mBorderSpace = borderSpace;
86     }
87 
getChildAt(int cellX, int cellY)88     public View getChildAt(int cellX, int cellY) {
89         final int count = getChildCount();
90         for (int i = 0; i < count; i++) {
91             View child = getChildAt(i);
92             CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
93 
94             if ((lp.getCellX() <= cellX) && (cellX < lp.getCellX() + lp.cellHSpan)
95                     && (lp.getCellY() <= cellY) && (cellY < lp.getCellY() + lp.cellVSpan)) {
96                 return child;
97             }
98         }
99         return null;
100     }
101 
102     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)103     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
104         int count = getChildCount();
105 
106         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
107         int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
108         setMeasuredDimension(widthSpecSize, heightSpecSize);
109 
110         for (int i = 0; i < count; i++) {
111             View child = getChildAt(i);
112             if (child.getVisibility() != GONE) {
113                 measureChild(child);
114             }
115         }
116     }
117 
118     /**
119      * Adds view to Layout a new position and it does not trigger a layout request.
120      * For more information check documentation for
121      * {@code ViewGroup#addViewInLayout(View, int, LayoutParams, boolean)}
122      *
123      * @param child view to be added
124      * @return true if the child was added, false otherwise
125      */
addViewInLayout(View child, LayoutParams layoutParams)126     public boolean addViewInLayout(View child, LayoutParams layoutParams) {
127         return super.addViewInLayout(child, -1, layoutParams, true);
128     }
129 
setupLp(View child)130     public void setupLp(View child) {
131         CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
132         if (child instanceof NavigableAppWidgetHostView) {
133             DeviceProfile profile = mActivity.getDeviceProfile();
134             final PointF appWidgetScale = profile.getAppWidgetScale((ItemInfo) child.getTag());
135             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
136                     appWidgetScale.x, appWidgetScale.y, mBorderSpace, profile.widgetPadding);
137         } else {
138             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
139                     mBorderSpace);
140         }
141     }
142 
143     // Set whether or not to invert the layout horizontally if the layout is in RTL mode.
setInvertIfRtl(boolean invert)144     public void setInvertIfRtl(boolean invert) {
145         mInvertIfRtl = invert;
146     }
147 
getCellContentHeight()148     public int getCellContentHeight() {
149         return Math.min(getMeasuredHeight(),
150                 mActivity.getDeviceProfile().getCellContentHeight(mContainerType));
151     }
152 
measureChild(View child)153     public void measureChild(View child) {
154         CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
155         final DeviceProfile dp = mActivity.getDeviceProfile();
156 
157         if (child instanceof NavigableAppWidgetHostView) {
158             final PointF appWidgetScale = dp.getAppWidgetScale((ItemInfo) child.getTag());
159             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
160                     appWidgetScale.x, appWidgetScale.y, mBorderSpace, dp.widgetPadding);
161         } else if (isChildQsb(child)) {
162             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
163                     mBorderSpace);
164             // No need to add padding for Qsb, which is either Smartspace (actual or preview), or
165             // QsbContainerView.
166         } else {
167             lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
168                     mBorderSpace);
169             // Center the icon/folder
170             int cHeight = getCellContentHeight();
171             int cellPaddingY =
172                     dp.cellYPaddingPx >= 0 && mContainerType == WORKSPACE
173                             ? dp.cellYPaddingPx
174                             : (int) Math.max(0, ((lp.height - cHeight) / 2f));
175 
176             // No need to add padding when cell layout border spacing is present.
177             boolean noPaddingX =
178                     (dp.cellLayoutBorderSpacePx.x > 0 && mContainerType == WORKSPACE)
179                             || (dp.folderCellLayoutBorderSpacePx.x > 0 && mContainerType == FOLDER)
180                             || (dp.hotseatBorderSpace > 0 && mContainerType == HOTSEAT);
181             int cellPaddingX = noPaddingX
182                     ? 0
183                     : mContainerType == WORKSPACE
184                             ? dp.workspaceCellPaddingXPx
185                             : (int) (dp.edgeMarginPx / 2f);
186             child.setPadding(cellPaddingX, cellPaddingY, cellPaddingX, 0);
187         }
188         int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
189         int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
190         child.measure(childWidthMeasureSpec, childheightMeasureSpec);
191     }
192 
isChildQsb(View child)193     private boolean isChildQsb(View child) {
194         return child.getId() == R.id.search_container_workspace;
195     }
196 
invertLayoutHorizontally()197     public boolean invertLayoutHorizontally() {
198         return mInvertIfRtl && Utilities.isRtl(getResources());
199     }
200 
201     @Override
onLayout(boolean changed, int l, int t, int r, int b)202     protected void onLayout(boolean changed, int l, int t, int r, int b) {
203         Trace.beginSection("ShortcutAndWidgetConteiner#onLayout");
204         int count = getChildCount();
205         for (int i = 0; i < count; i++) {
206             final View child = getChildAt(i);
207             if (child.getVisibility() != GONE) {
208                 layoutChild(child);
209             }
210         }
211         Trace.endSection();
212     }
213 
214     /**
215      * Core logic to layout a child for this ViewGroup.
216      */
layoutChild(View child)217     public void layoutChild(View child) {
218         CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
219         if (child instanceof NavigableAppWidgetHostView) {
220             NavigableAppWidgetHostView nahv = (NavigableAppWidgetHostView) child;
221 
222             // Scale and center the widget to fit within its cells.
223             DeviceProfile profile = mActivity.getDeviceProfile();
224             final PointF appWidgetScale = profile.getAppWidgetScale((ItemInfo) child.getTag());
225             float scaleX = appWidgetScale.x;
226             float scaleY = appWidgetScale.y;
227 
228             nahv.setScaleToFit(Math.min(scaleX, scaleY));
229             nahv.getTranslateDelegate().setTranslation(INDEX_WIDGET_CENTERING,
230                     -(lp.width - (lp.width * scaleX)) / 2.0f,
231                     -(lp.height - (lp.height * scaleY)) / 2.0f);
232         }
233 
234         int childLeft = lp.x;
235         int childTop = lp.y;
236 
237         // We want to get the layout position of the widget, but layout() is a final function in
238         // ViewGroup which makes it impossible to be overridden. Overriding onLayout() will have no
239         // effect since it will not be called when the transition is enabled. The only possible
240         // solution here seems to be sending the positions when CellLayout is laying out the views
241         if (child instanceof LauncherAppWidgetHostView widgetView
242                 && widgetView.getCellChildViewPreLayoutListener() != null) {
243             widgetView.getCellChildViewPreLayoutListener().notifyBoundChangeOnPreLayout(child,
244                     childLeft, childTop, childLeft + lp.width, childTop + lp.height);
245         }
246         child.layout(childLeft, childTop, childLeft + lp.width, childTop + lp.height);
247         if (mTranslationProvider != null) {
248             final float tx = mTranslationProvider.getTranslationX(child);
249             if (child instanceof Reorderable) {
250                 ((Reorderable) child).getTranslateDelegate()
251                         .getTranslationX(INDEX_BUBBLE_ADJUSTMENT_ANIM)
252                         .setValue(tx);
253             } else {
254                 child.setTranslationX(tx);
255             }
256         }
257 
258         if (lp.dropped) {
259             lp.dropped = false;
260 
261             final int[] cellXY = mTmpCellXY;
262             getLocationOnScreen(cellXY);
263             mWallpaperManager.sendWallpaperCommand(getWindowToken(),
264                     WallpaperManager.COMMAND_DROP,
265                     cellXY[0] + childLeft + lp.width / 2,
266                     cellXY[1] + childTop + lp.height / 2, 0, null);
267         }
268     }
269 
270 
271     @Override
onInterceptTouchEvent(MotionEvent ev)272     public boolean onInterceptTouchEvent(MotionEvent ev) {
273         if (ev.getAction() == ACTION_DOWN && getAlpha() == 0) {
274             // Dont let children handle touch, if we are not visible.
275             return true;
276         }
277         return super.onInterceptTouchEvent(ev);
278     }
279 
280     @Override
shouldDelayChildPressedState()281     public boolean shouldDelayChildPressedState() {
282         return false;
283     }
284 
285     @Override
requestChildFocus(View child, View focused)286     public void requestChildFocus(View child, View focused) {
287         super.requestChildFocus(child, focused);
288         if (child != null) {
289             Rect r = new Rect();
290             child.getDrawingRect(r);
291             requestRectangleOnScreen(r);
292         }
293     }
294 
295     @Override
cancelLongPress()296     public void cancelLongPress() {
297         super.cancelLongPress();
298 
299         // Cancel long press for all children
300         final int count = getChildCount();
301         for (int i = 0; i < count; i++) {
302             final View child = getChildAt(i);
303             child.cancelLongPress();
304         }
305     }
306 
307     @Override
drawFolderLeaveBehindForIcon(FolderIcon child)308     public void drawFolderLeaveBehindForIcon(FolderIcon child) {
309         CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
310         // While the folder is open, the position of the icon cannot change.
311         lp.canReorder = false;
312         if (mContainerType == HOTSEAT) {
313             CellLayout cl = (CellLayout) getParent();
314             cl.setFolderLeaveBehindCell(lp.getCellX(), lp.getCellY());
315         }
316     }
317 
318     @Override
clearFolderLeaveBehind(FolderIcon child)319     public void clearFolderLeaveBehind(FolderIcon child) {
320         ((CellLayoutLayoutParams) child.getLayoutParams()).canReorder = true;
321         if (mContainerType == HOTSEAT) {
322             CellLayout cl = (CellLayout) getParent();
323             cl.clearFolderLeaveBehind();
324         }
325     }
326 
setTranslationProvider(@ullable TranslationProvider provider)327     void setTranslationProvider(@Nullable TranslationProvider provider) {
328         mTranslationProvider = provider;
329     }
330 
331     /** Provides translation values to apply when laying out child views. */
332     interface TranslationProvider {
getTranslationX(View child)333         float getTranslationX(View child);
334     }
335 }
336