diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 68c3851bf9..a0e87cfcbc 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -130,4 +130,5 @@ 14dp 1dp 24dp + 16dp diff --git a/quickstep/res/values/styles.xml b/quickstep/res/values/styles.xml index 5a353f0a59..df089f6738 100644 --- a/quickstep/res/values/styles.xml +++ b/quickstep/res/values/styles.xml @@ -89,6 +89,5 @@ \ No newline at end of file diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index edcd0a203a..d1fa2fd3ef 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -30,7 +30,6 @@ import android.content.Intent; import android.content.IntentSender; import android.os.Bundle; import android.os.CancellationSignal; -import android.view.LayoutInflater; import android.view.View; import androidx.annotation.Nullable; @@ -43,7 +42,7 @@ import com.android.launcher3.proxy.StartActivityParams; import com.android.launcher3.statehandlers.BackButtonAlphaHandler; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager.StateHandler; -import com.android.launcher3.taskbar.TaskbarContainerView; +import com.android.launcher3.taskbar.TaskbarActivityContext; import com.android.launcher3.taskbar.TaskbarController; import com.android.launcher3.taskbar.TaskbarStateHandler; import com.android.launcher3.uioverrides.RecentsViewStateController; @@ -207,6 +206,7 @@ public abstract class BaseQuickstepLauncher extends Launcher mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this)); addTaskbarIfNecessary(); + addOnDeviceProfileChangeListener(newDp -> addTaskbarIfNecessary()); } @Override @@ -223,9 +223,9 @@ public abstract class BaseQuickstepLauncher extends Launcher mTaskbarController = null; } if (FeatureFlags.ENABLE_TASKBAR.get() && mDeviceProfile.isTablet) { - TaskbarContainerView taskbarContainer = (TaskbarContainerView) LayoutInflater.from(this) - .inflate(R.layout.taskbar, null, false); - mTaskbarController = new TaskbarController(this, taskbarContainer); + TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this); + mTaskbarController = new TaskbarController(this, + taskbarActivityContext.getTaskbarContainerView()); mTaskbarController.init(); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java new file mode 100644 index 0000000000..06372fe22f --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +import android.content.ContextWrapper; +import android.graphics.Rect; +import android.view.LayoutInflater; + +import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.R; +import com.android.launcher3.views.ActivityContext; +import com.android.launcher3.views.BaseDragLayer; + +/** + * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements + * that are used by both Launcher and Taskbar (such as Folder) to reference a generic + * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer. + */ +public class TaskbarActivityContext extends ContextWrapper implements ActivityContext { + + private final DeviceProfile mDeviceProfile; + private final LayoutInflater mLayoutInflater; + private final TaskbarContainerView mTaskbarContainerView; + + public TaskbarActivityContext(BaseQuickstepLauncher launcher) { + super(launcher); + mDeviceProfile = launcher.getDeviceProfile().copy(this); + float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size); + float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx; + mDeviceProfile.updateIconSize(iconScale, getResources()); + + mLayoutInflater = LayoutInflater.from(this).cloneInContext(this); + + mTaskbarContainerView = (TaskbarContainerView) mLayoutInflater + .inflate(R.layout.taskbar, null, false); + } + + public TaskbarContainerView getTaskbarContainerView() { + return mTaskbarContainerView; + } + + /** + * @return A LayoutInflater to use in this Context. Views inflated with this LayoutInflater will + * be able to access this TaskbarActivityContext via ActivityContext.lookupContext(). + */ + public LayoutInflater getLayoutInflater() { + return mLayoutInflater; + } + + @Override + public BaseDragLayer getDragLayer() { + return mTaskbarContainerView; + } + + @Override + public DeviceProfile getDeviceProfile() { + return mDeviceProfile; + } + + @Override + public Rect getFolderBoundingBox() { + return mTaskbarContainerView.getFolderBoundingBox(); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java index 3b361c4a38..ddd0d15442 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java @@ -19,19 +19,29 @@ import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsI import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION; import android.content.Context; +import android.graphics.Rect; import android.util.AttributeSet; -import android.widget.FrameLayout; +import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.R; import com.android.launcher3.anim.AlphaUpdateListener; +import com.android.launcher3.util.TouchController; +import com.android.launcher3.views.BaseDragLayer; import com.android.systemui.shared.system.ViewTreeObserverWrapper; /** * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder. */ -public class TaskbarContainerView extends FrameLayout { +public class TaskbarContainerView extends BaseDragLayer { + + private final int[] mTempLoc = new int[2]; + private final int mFolderMargin; + + // Initialized in TaskbarController constructor. + private TaskbarController.TaskbarContainerViewCallbacks mControllerCallbacks; // Initialized in init. private TaskbarView mTaskbarView; @@ -52,12 +62,23 @@ public class TaskbarContainerView extends FrameLayout { public TaskbarContainerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); + super(context, attrs, 1 /* alphaChannelCount */); + mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin); + } + + protected void construct(TaskbarController.TaskbarContainerViewCallbacks callbacks) { + mControllerCallbacks = callbacks; } protected void init(TaskbarView taskbarView) { mTaskbarView = taskbarView; mTaskbarInsetsComputer = createTaskbarInsetsComputer(); + recreateControllers(); + } + + @Override + public void recreateControllers() { + mControllers = new TouchController[0]; } private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() { @@ -70,6 +91,17 @@ public class TaskbarContainerView extends FrameLayout { // We're visible again, accept touches anywhere in our bounds. insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME); } + + // TaskbarContainerView provides insets to other apps based on contentInsets. These + // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g. + // to show a floating view like Folder. Thus, we set the contentInsets to be where + // mTaskbarView is, since its position never changes and insets rather than overlays. + int[] loc = mTempLoc; + mTaskbarView.getLocationInWindow(loc); + insetsInfo.contentInsets.left = loc[0]; + insetsInfo.contentInsets.top = loc[1]; + insetsInfo.contentInsets.right = getWidth() - (loc[0] + mTaskbarView.getWidth()); + insetsInfo.contentInsets.bottom = getHeight() - (loc[1] + mTaskbarView.getHeight()); }; } @@ -91,4 +123,30 @@ public class TaskbarContainerView extends FrameLayout { cleanup(); } + + @Override + protected boolean canFindActiveController() { + // Unlike super class, we want to be able to find controllers when touches occur in the + // gesture area. For example, this allows Folder to close itself when touching the Taskbar. + return true; + } + + @Override + public void onViewRemoved(View child) { + super.onViewRemoved(child); + mControllerCallbacks.onViewRemoved(); + } + + /** + * @return Bounds (in our coordinates) where an opened Folder can display. + */ + protected Rect getFolderBoundingBox() { + Rect boundingBox = new Rect(0, 0, getWidth(), getHeight() - mTaskbarView.getHeight()); + boundingBox.inset(mFolderMargin, mFolderMargin); + return boundingBox; + } + + protected TaskbarActivityContext getTaskbarActivityContext() { + return mActivity; + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java index 260428dbc2..ab05fbf036 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java @@ -34,11 +34,15 @@ import android.view.WindowManager; import androidx.annotation.Nullable; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; import com.android.launcher3.QuickstepAppTransitionManagerImpl; import com.android.launcher3.R; import com.android.launcher3.anim.PendingAnimation; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.states.StateAnimationConfig; import com.android.launcher3.touch.ItemClickHandler; @@ -81,8 +85,9 @@ public class TaskbarController { TaskbarContainerView taskbarContainerView) { mLauncher = launcher; mTaskbarContainerView = taskbarContainerView; + mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks()); mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view); - mTaskbarView.setCallbacks(createTaskbarViewCallbacks()); + mTaskbarView.construct(createTaskbarViewCallbacks()); mWindowManager = mLauncher.getWindowManager(); mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getResources().getDimensionPixelSize(R.dimen.taskbar_size)); @@ -110,6 +115,18 @@ public class TaskbarController { }; } + private TaskbarContainerViewCallbacks createTaskbarContainerViewCallbacks() { + return new TaskbarContainerViewCallbacks() { + @Override + public void onViewRemoved() { + if (mTaskbarContainerView.getChildCount() == 1) { + // Only TaskbarView remains. + setTaskbarWindowFullscreen(false); + } + } + }; + } + private TaskbarViewCallbacks createTaskbarViewCallbacks() { return new TaskbarViewCallbacks() { @Override @@ -120,9 +137,29 @@ public class TaskbarController { Task task = (Task) tag; ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key, ActivityOptions.makeBasic()); + } else if (tag instanceof FolderInfo) { + FolderIcon folderIcon = (FolderIcon) view; + Folder folder = folderIcon.getFolder(); + + setTaskbarWindowFullscreen(true); + + mTaskbarContainerView.post(() -> { + folder.animateOpen(); + + folder.iterateOverItems((itemInfo, itemView) -> { + itemView.setOnClickListener(getItemOnClickListener()); + itemView.setOnLongClickListener(getItemOnLongClickListener()); + // To play haptic when dragging, like other Taskbar items do. + itemView.setHapticFeedbackEnabled(true); + return false; + }); + }); } else { ItemClickHandler.INSTANCE.onClick(view); } + + AbstractFloatingView.closeAllOpenViews( + mTaskbarContainerView.getTaskbarActivityContext()); }; } @@ -344,6 +381,20 @@ public class TaskbarController { return mTaskbarContainerView.getWindowId().equals(v.getWindowId()); } + /** + * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size. + */ + private void setTaskbarWindowFullscreen(boolean fullscreen) { + if (fullscreen) { + mWindowLayoutParams.width = MATCH_PARENT; + mWindowLayoutParams.height = MATCH_PARENT; + } else { + mWindowLayoutParams.width = mTaskbarSize.x; + mWindowLayoutParams.height = mTaskbarSize.y; + } + mWindowManager.updateViewLayout(mTaskbarContainerView, mWindowLayoutParams); + } + /** * Contains methods that TaskbarStateHandler can call to interface with TaskbarController. */ @@ -360,6 +411,13 @@ public class TaskbarController { void updateTaskbarVisibilityAlpha(float alpha); } + /** + * Contains methods that TaskbarContainerView can call to interface with TaskbarController. + */ + protected interface TaskbarContainerViewCallbacks { + void onViewRemoved(); + } + /** * Contains methods that TaskbarView can call to interface with TaskbarController. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index d8f3bb595e..df77f8778e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -22,7 +22,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.DragEvent; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -32,10 +31,14 @@ import androidx.annotation.LayoutRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.BubbleTextView; import com.android.launcher3.R; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.views.ActivityContext; import com.android.systemui.shared.recents.model.Task; /** @@ -51,6 +54,9 @@ public class TaskbarView extends LinearLayout { private final RectF mDelegateSlopBounds = new RectF(); private final int[] mTempOutLocation = new int[2]; + // Initialized in TaskbarController constructor. + private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; + // Initialized in init(). private int mHotseatStartIndex; private int mHotseatEndIndex; @@ -58,8 +64,6 @@ public class TaskbarView extends LinearLayout { private int mRecentsStartIndex; private int mRecentsEndIndex; - private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; - // Delegate touches to the closest view if within mIconTouchSize. private boolean mDelegateTargeted; private View mDelegateView; @@ -90,7 +94,7 @@ public class TaskbarView extends LinearLayout { mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } - protected void setCallbacks(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) { + protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks) { mControllerCallbacks = taskbarViewCallbacks; } @@ -130,17 +134,37 @@ public class TaskbarView extends LinearLayout { // Replace any Hotseat views with the appropriate type if it's not already that type. final int expectedLayoutResId; + boolean isFolder = false; + boolean needsReinflate = false; if (hotseatItemInfo != null && hotseatItemInfo.isPredictedItem()) { expectedLayoutResId = R.layout.taskbar_predicted_app_icon; + } else if (hotseatItemInfo instanceof FolderInfo) { + expectedLayoutResId = R.layout.folder_icon; + isFolder = true; + // Unlike for BubbleTextView, we can't reapply a new FolderInfo after inflation, so + // if the info changes we need to reinflate. This should only happen if a new folder + // is dragged to the position that another folder previously existed. + needsReinflate = hotseatView != null && hotseatView.getTag() != hotseatItemInfo; } else { expectedLayoutResId = R.layout.taskbar_app_icon; } - if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId) { + if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId + || needsReinflate) { removeView(hotseatView); - BubbleTextView btv = (BubbleTextView) inflate(expectedLayoutResId); - LayoutParams lp = new LayoutParams(btv.getIconSize(), btv.getIconSize()); + TaskbarActivityContext activityContext = + ActivityContext.lookupContext(getContext()); + if (isFolder) { + FolderInfo folderInfo = (FolderInfo) hotseatItemInfo; + FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId, + activityContext, this, folderInfo); + folderIcon.setTextVisible(false); + hotseatView = folderIcon; + } else { + hotseatView = inflate(expectedLayoutResId); + } + int iconSize = activityContext.getDeviceProfile().iconSizePx; + LayoutParams lp = new LayoutParams(iconSize, iconSize); lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0); - hotseatView = btv; addView(hotseatView, hotseatIndex, lp); } @@ -153,6 +177,11 @@ public class TaskbarView extends LinearLayout { hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener()); hotseatView.setOnLongClickListener( mControllerCallbacks.getItemOnLongClickListener()); + } else if (isFolder) { + hotseatView.setVisibility(VISIBLE); + hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener()); + hotseatView.setOnLongClickListener( + mControllerCallbacks.getItemOnLongClickListener()); } else { hotseatView.setVisibility(GONE); hotseatView.setOnClickListener(null); @@ -345,6 +374,7 @@ public class TaskbarView extends LinearLayout { switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: mIsDraggingItem = true; + AbstractFloatingView.closeAllOpenViews(ActivityContext.lookupContext(getContext())); return true; case DragEvent.ACTION_DRAG_ENDED: mIsDraggingItem = false; @@ -358,6 +388,7 @@ public class TaskbarView extends LinearLayout { } private View inflate(@LayoutRes int layoutResId) { - return LayoutInflater.from(getContext()).inflate(layoutResId, this, false); + TaskbarActivityContext taskbarActivityContext = ActivityContext.lookupContext(getContext()); + return taskbarActivityContext.getLayoutInflater().inflate(layoutResId, this, false); } } diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index edc7e9bb8a..e5a4335dcd 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -505,7 +505,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, * @param canvas The canvas to draw to. */ protected void drawDotIfNecessary(Canvas canvas) { - if (mDisplay == DISPLAY_TASKBAR) { + if (mActivity instanceof Launcher && ((Launcher) mActivity).isViewInTaskbar(this)) { // TODO: support notification dots in Taskbar return; } diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 19915b7026..83a7d77601 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -375,7 +375,7 @@ public class DeviceProfile { * iconTextSizePx, iconDrawablePaddingPx, cellWidth/Height, allApps* variants, * hotseat sizes, workspaceSpringLoadedShrinkFactor, folderIconSizePx, and folderIconOffsetYPx. */ - private void updateIconSize(float scale, Resources res) { + public void updateIconSize(float scale, Resources res) { // Workspace final boolean isVerticalLayout = isVerticalBarLayout(); float invIconSizeDp = isVerticalLayout ? inv.landscapeIconSize : inv.iconSize; diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java index 9efbc7dcab..504b29e75a 100644 --- a/src/com/android/launcher3/folder/Folder.java +++ b/src/com/android/launcher3/folder/Folder.java @@ -1616,6 +1616,11 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo return false; } + @Override + public boolean canInterceptEventsInSystemGestureRegion() { + return true; + } + /** * Alternative to using {@link #getClipToOutline()} as it only works with derivatives of * rounded rect. diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 65a4fba63c..75d8f2296c 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -754,6 +754,9 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } public void clearLeaveBehindIfExists() { + if (!(getLayoutParams() instanceof CellLayout.LayoutParams)) { + return; + } ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true; if (isInHotseat()) { CellLayout cl = (CellLayout) getParent().getParent(); @@ -762,6 +765,9 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel } public void drawLeaveBehindIfExists() { + if (!(getLayoutParams() instanceof CellLayout.LayoutParams)) { + return; + } CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams(); // While the folder is open, the position of the icon cannot change. lp.canReorder = false; diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java index 15f7730fd7..1939d15776 100644 --- a/src/com/android/launcher3/views/BaseDragLayer.java +++ b/src/com/android/launcher3/views/BaseDragLayer.java @@ -206,15 +206,19 @@ public abstract class BaseDragLayer protected boolean findActiveController(MotionEvent ev) { mActiveController = null; - if ((mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION - | TOUCH_DISPATCHING_FROM_PROXY)) == 0) { - // Only look for controllers if we are not dispatching from gesture area and proxy is - // not active + if (canFindActiveController()) { mActiveController = findControllerToHandleTouch(ev); } return mActiveController != null; } + protected boolean canFindActiveController() { + // Only look for controllers if we are not dispatching from gesture area and proxy is + // not active + return (mTouchDispatchState & (TOUCH_DISPATCHING_FROM_VIEW_GESTURE_REGION + | TOUCH_DISPATCHING_FROM_PROXY)) == 0; + } + @Override public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) { // Shortcuts can appear above folder