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