/* * Copyright (C) 2019 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.touch; import static android.view.Gravity.CENTER_VERTICAL; import static android.view.Gravity.END; import static android.view.Gravity.START; import static android.view.Gravity.TOP; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT; import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN; import android.content.res.Resources; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.ShapeDrawable; import android.util.FloatProperty; import android.util.Pair; import android.view.MotionEvent; import android.view.Surface; import android.view.VelocityTracker; import android.view.View; import android.view.accessibility.AccessibilityEvent; import android.widget.FrameLayout; import android.widget.LinearLayout; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.util.SplitConfigurationOptions; import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption; import com.android.launcher3.util.SplitConfigurationOptions.StagePosition; import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds; import com.android.launcher3.views.BaseDragLayer; import java.util.Collections; import java.util.List; public class LandscapePagedViewHandler implements PagedOrientationHandler { @Override public T getPrimaryValue(T x, T y) { return y; } @Override public T getSecondaryValue(T x, T y) { return x; } @Override public int getPrimaryValue(int x, int y) { return y; } @Override public int getSecondaryValue(int x, int y) { return x; } @Override public float getPrimaryValue(float x, float y) { return y; } @Override public float getSecondaryValue(float x, float y) { return x; } @Override public boolean isLayoutNaturalToLauncher() { return false; } @Override public void adjustFloatingIconStartVelocity(PointF velocity) { float oldX = velocity.x; float oldY = velocity.y; velocity.set(-oldY, oldX); } @Override public void setPrimary(T target, Int2DAction action, int param) { action.call(target, 0, param); } @Override public void setPrimary(T target, Float2DAction action, float param) { action.call(target, 0, param); } @Override public void setSecondary(T target, Float2DAction action, float param) { action.call(target, param, 0); } @Override public void set(T target, Int2DAction action, int primaryParam, int secondaryParam) { action.call(target, secondaryParam, primaryParam); } @Override public float getPrimaryDirection(MotionEvent event, int pointerIndex) { return event.getY(pointerIndex); } @Override public float getPrimaryVelocity(VelocityTracker velocityTracker, int pointerId) { return velocityTracker.getYVelocity(pointerId); } @Override public int getMeasuredSize(View view) { return view.getMeasuredHeight(); } @Override public int getPrimarySize(View view) { return view.getHeight(); } @Override public float getPrimarySize(RectF rect) { return rect.height(); } @Override public float getStart(RectF rect) { return rect.top; } @Override public float getEnd(RectF rect) { return rect.bottom; } @Override public int getClearAllSidePadding(View view, boolean isRtl) { return (isRtl ? view.getPaddingBottom() : - view.getPaddingTop()) / 2; } @Override public int getSecondaryDimension(View view) { return view.getWidth(); } @Override public FloatProperty getPrimaryViewTranslate() { return VIEW_TRANSLATE_Y; } @Override public FloatProperty getSecondaryViewTranslate() { return VIEW_TRANSLATE_X; } @Override public int getSplitTaskViewDismissDirection(@StagePosition int stagePosition, DeviceProfile dp) { // Don't use device profile here because we know we're in fake landscape, only split option // available is top/left if (stagePosition == STAGE_POSITION_TOP_OR_LEFT) { // Top (visually left) side return SPLIT_TRANSLATE_PRIMARY_NEGATIVE; } throw new IllegalStateException("Invalid split stage position: " + stagePosition); } @Override public int getPrimaryScroll(View view) { return view.getScrollY(); } @Override public float getPrimaryScale(View view) { return view.getScaleY(); } @Override public void setMaxScroll(AccessibilityEvent event, int maxScroll) { event.setMaxScrollY(maxScroll); } @Override public boolean getRecentsRtlSetting(Resources resources) { return !Utilities.isRtl(resources); } @Override public float getDegreesRotated() { return 90; } @Override public int getRotation() { return Surface.ROTATION_90; } @Override public void setPrimaryScale(View view, float scale) { view.setScaleY(scale); } @Override public void setSecondaryScale(View view, float scale) { view.setScaleX(scale); } @Override public int getChildStart(View view) { return view.getTop(); } @Override public int getCenterForPage(View view, Rect insets) { return (view.getPaddingLeft() + view.getMeasuredWidth() + insets.left - insets.right - view.getPaddingRight()) / 2; } @Override public int getScrollOffsetStart(View view, Rect insets) { return insets.top + view.getPaddingTop(); } @Override public int getScrollOffsetEnd(View view, Rect insets) { return view.getHeight() - view.getPaddingBottom() - insets.bottom; } public int getSecondaryTranslationDirectionFactor() { return 1; } @Override public int getSplitTranslationDirectionFactor(int stagePosition, DeviceProfile deviceProfile) { if (stagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT) { return -1; } else { return 1; } } @Override public float getTaskMenuX(float x, View thumbnailView, int overScroll, DeviceProfile deviceProfile) { return thumbnailView.getMeasuredWidth() + x; } @Override public float getTaskMenuY(float y, View thumbnailView, int overScroll) { return y + overScroll + (thumbnailView.getMeasuredHeight() - thumbnailView.getMeasuredWidth()) / 2f; } @Override public int getTaskMenuWidth(View view, DeviceProfile deviceProfile) { return view.getMeasuredWidth(); } @Override public void setTaskOptionsMenuLayoutOrientation(DeviceProfile deviceProfile, LinearLayout taskMenuLayout, int dividerSpacing, ShapeDrawable dividerDrawable) { taskMenuLayout.setOrientation(LinearLayout.VERTICAL); dividerDrawable.setIntrinsicHeight(dividerSpacing); taskMenuLayout.setDividerDrawable(dividerDrawable); } @Override public void setLayoutParamsForTaskMenuOptionItem(LinearLayout.LayoutParams lp, LinearLayout viewGroup, DeviceProfile deviceProfile) { // Phone fake landscape viewGroup.setOrientation(LinearLayout.HORIZONTAL); lp.width = MATCH_PARENT; lp.height = WRAP_CONTENT; } @Override public void setTaskMenuAroundTaskView(LinearLayout taskView, float margin) { BaseDragLayer.LayoutParams lp = (BaseDragLayer.LayoutParams) taskView.getLayoutParams(); lp.topMargin += margin; } @Override public PointF getAdditionalInsetForTaskMenu(float margin) { return new PointF(margin, 0); } @Override public Pair setDwbLayoutParamsAndGetTranslations(int taskViewWidth, int taskViewHeight, StagedSplitBounds splitBounds, DeviceProfile deviceProfile, View[] thumbnailViews, int desiredTaskId, View banner) { float translationX = 0; float translationY = 0; FrameLayout.LayoutParams bannerParams = (FrameLayout.LayoutParams) banner.getLayoutParams(); banner.setPivotX(0); banner.setPivotY(0); banner.setRotation(getDegreesRotated()); translationX = banner.getHeight(); FrameLayout.LayoutParams snapshotParams = (FrameLayout.LayoutParams) thumbnailViews[0] .getLayoutParams(); bannerParams.gravity = TOP | START; if (splitBounds == null) { // Single, fullscreen case bannerParams.width = taskViewHeight - snapshotParams.topMargin; return new Pair<>(translationX, Integer.valueOf(snapshotParams.topMargin).floatValue()); } // Set correct width if (desiredTaskId == splitBounds.leftTopTaskId) { bannerParams.width = thumbnailViews[0].getMeasuredHeight(); } else { bannerParams.width = thumbnailViews[1].getMeasuredHeight(); } // Set translations if (desiredTaskId == splitBounds.rightBottomTaskId) { translationY = (snapshotParams.topMargin + taskViewHeight) * (splitBounds.leftTaskPercent) + (taskViewHeight * splitBounds.dividerWidthPercent); } if (desiredTaskId == splitBounds.leftTopTaskId) { translationY = snapshotParams.topMargin; } return new Pair<>(translationX, translationY); } /* ---------- The following are only used by TaskViewTouchHandler. ---------- */ @Override public SingleAxisSwipeDetector.Direction getUpDownSwipeDirection() { return HORIZONTAL; } @Override public int getUpDirection(boolean isRtl) { return isRtl ? SingleAxisSwipeDetector.DIRECTION_NEGATIVE : SingleAxisSwipeDetector.DIRECTION_POSITIVE; } @Override public boolean isGoingUp(float displacement, boolean isRtl) { return isRtl ? displacement < 0 : displacement > 0; } @Override public int getTaskDragDisplacementFactor(boolean isRtl) { return isRtl ? 1 : -1; } /* -------------------- */ @Override public ChildBounds getChildBounds(View child, int childStart, int pageCenter, boolean layoutChild) { final int childHeight = child.getMeasuredHeight(); final int childBottom = childStart + childHeight; final int childWidth = child.getMeasuredWidth(); final int childLeft = pageCenter - childWidth/ 2; if (layoutChild) { child.layout(childLeft, childStart, childLeft + childWidth, childBottom); } return new ChildBounds(childHeight, childWidth, childBottom, childLeft); } @SuppressWarnings("SuspiciousNameCombination") @Override public int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect) { return rect.left; } @Override public List getSplitPositionOptions(DeviceProfile dp) { // Add "left" side of phone which is actually the top return Collections.singletonList(new SplitPositionOption( R.drawable.ic_split_left, R.string.split_screen_position_left, STAGE_POSITION_TOP_OR_LEFT, STAGE_TYPE_MAIN)); } @Override public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp, @StagePosition int stagePosition, Rect out) { // In fake land/seascape, the placeholder always needs to go to the "top" of the device, // which is the same bounds as 0 rotation. int width = dp.widthPx; out.set(0, 0, width, placeholderHeight); } @Override public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp, @StagePosition int stagePosition, Rect out1, Rect out2) { // In fake land/seascape, the window bounds are always top and bottom half int screenHeight = dp.heightPx; int screenWidth = dp.widthPx; out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize); out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight); } @Override public void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, StagedSplitBounds splitInfo, int desiredStagePosition) { float diff; float horizontalDividerDiff = splitInfo.visualDividerBounds.width() / 2f; if (desiredStagePosition == SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT) { diff = outRect.height() * (1f - splitInfo.leftTaskPercent) + horizontalDividerDiff; outRect.bottom -= diff; } else { diff = outRect.height() * splitInfo.leftTaskPercent + horizontalDividerDiff; outRect.top += diff; } } @Override public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot, int parentWidth, int parentHeight, StagedSplitBounds splitBoundsConfig, DeviceProfile dp) { int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx; int totalThumbnailHeight = parentHeight - spaceAboveSnapshot; int dividerBar = splitBoundsConfig.visualDividerBounds.width(); int primarySnapshotHeight; int primarySnapshotWidth; int secondarySnapshotHeight; int secondarySnapshotWidth; float taskPercent = splitBoundsConfig.appsStackedVertically ? splitBoundsConfig.topTaskPercent : splitBoundsConfig.leftTaskPercent; primarySnapshotWidth = parentWidth; primarySnapshotHeight = (int) (totalThumbnailHeight * taskPercent); secondarySnapshotWidth = parentWidth; secondarySnapshotHeight = totalThumbnailHeight - primarySnapshotHeight - dividerBar; secondarySnapshot.setTranslationY(primarySnapshotHeight + spaceAboveSnapshot + dividerBar); primarySnapshot.measure( View.MeasureSpec.makeMeasureSpec(primarySnapshotWidth, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(primarySnapshotHeight, View.MeasureSpec.EXACTLY)); secondarySnapshot.measure( View.MeasureSpec.makeMeasureSpec(secondarySnapshotWidth, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(secondarySnapshotHeight, View.MeasureSpec.EXACTLY)); } @Override public void setIconAndSnapshotParams(View iconView, int taskIconMargin, int taskIconHeight, FrameLayout.LayoutParams snapshotParams, boolean isRtl) { FrameLayout.LayoutParams iconParams = (FrameLayout.LayoutParams) iconView.getLayoutParams(); iconParams.gravity = (isRtl ? START : END) | CENTER_VERTICAL; iconParams.rightMargin = -taskIconHeight - taskIconMargin / 2; iconParams.leftMargin = 0; iconParams.topMargin = snapshotParams.topMargin / 2; } @Override public void setSplitIconParams(View primaryIconView, View secondaryIconView, int taskIconHeight, int primarySnapshotWidth, int primarySnapshotHeight, boolean isRtl, DeviceProfile deviceProfile, StagedSplitBounds splitConfig) { FrameLayout.LayoutParams primaryIconParams = (FrameLayout.LayoutParams) primaryIconView.getLayoutParams(); FrameLayout.LayoutParams secondaryIconParams = new FrameLayout.LayoutParams(primaryIconParams); int dividerBar = (splitConfig.appsStackedVertically ? splitConfig.visualDividerBounds.height() : splitConfig.visualDividerBounds.width()); primaryIconParams.gravity = (isRtl ? START : END) | TOP; primaryIconView.setTranslationY(primarySnapshotHeight - primaryIconView.getHeight() / 2f); primaryIconView.setTranslationX(0); secondaryIconParams.gravity = (isRtl ? START : END) | TOP; secondaryIconView.setTranslationY(primarySnapshotHeight + taskIconHeight + dividerBar); secondaryIconView.setTranslationX(0); primaryIconView.setLayoutParams(primaryIconParams); secondaryIconView.setLayoutParams(secondaryIconParams); } @Override public int getDefaultSplitPosition(DeviceProfile deviceProfile) { throw new IllegalStateException("Default position not available in fake landscape"); } @Override public Pair getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary, DeviceProfile deviceProfile) { return new Pair<>(primary, secondary); } }