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