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