diff --git a/quickstep/res/layout/split_instructions_view.xml b/quickstep/res/layout/split_instructions_view.xml index 91fb05cd0a..5f037f8971 100644 --- a/quickstep/res/layout/split_instructions_view.xml +++ b/quickstep/res/layout/split_instructions_view.xml @@ -24,12 +24,15 @@ android:paddingTop="@dimen/split_instructions_vertical_padding" android:paddingBottom="@dimen/split_instructions_vertical_padding" android:elevation="@dimen/split_instructions_elevation" - android:visibility="gone"> + android:visibility="gone" + android:importantForAccessibility="yes"> \ No newline at end of file diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index 77799e6e7b..dd9fc6371b 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -230,6 +230,7 @@ Split Tap another app to use split screen + Exit split screen selection Choose another app to use split screen diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index fbe0a8f722..6c73a2de81 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -544,17 +544,9 @@ public class QuickstepLauncher extends Launcher { ArrayList list = new ArrayList<>(); list.add(getDragController()); - Consumer splitAnimator = animatorSet -> { - AnimatorSet anim = mSplitSelectStateController.getSplitAnimationController() - .createPlaceholderDismissAnim(QuickstepLauncher.this); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mSplitSelectStateController.resetState(); - } - }); - animatorSet.play(anim); - }; + Consumer splitAnimator = animatorSet -> + animatorSet.play(mSplitSelectStateController.getSplitAnimationController() + .createPlaceholderDismissAnim(this)); switch (mode) { case NO_BUTTON: list.add(new NoButtonQuickSwitchTouchController(this)); @@ -673,6 +665,8 @@ public class QuickstepLauncher extends Launcher { mSplitSelectStateController.resetState(); } }); + anim.add(mSplitSelectStateController.getSplitAnimationController() + .getShowSplitInstructionsAnim(this).buildAnim()); anim.buildAnim().start(); } diff --git a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt index 5740991a4a..56d68570ac 100644 --- a/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt +++ b/quickstep/src/com/android/quickstep/util/SplitAnimationController.kt @@ -26,15 +26,17 @@ import android.graphics.Rect import android.graphics.RectF import android.graphics.drawable.Drawable import android.view.View +import com.android.app.animation.Interpolators import com.android.launcher3.DeviceProfile -import com.android.launcher3.Launcher import com.android.launcher3.Utilities import com.android.launcher3.anim.PendingAnimation -import com.android.launcher3.dragndrop.DragLayer +import com.android.launcher3.statemanager.StatefulActivity import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource +import com.android.launcher3.views.BaseDragLayer import com.android.quickstep.views.FloatingTaskView import com.android.quickstep.views.IconView import com.android.quickstep.views.RecentsView +import com.android.quickstep.views.SplitInstructionsView import com.android.quickstep.views.TaskThumbnailView import com.android.quickstep.views.TaskView import com.android.quickstep.views.TaskView.TaskIdAttributeContainer @@ -58,6 +60,8 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC ) } + var splitInstructionsView: SplitInstructionsView? = null + /** * Returns different elements to animate for the initial split selection animation * depending on the state of the surface from which the split was initiated @@ -188,31 +192,26 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC } /** Does not play any animation if user is not currently in split selection state. */ - fun playPlaceholderDismissAnim(launcher: Launcher) { + fun playPlaceholderDismissAnim(launcher: StatefulActivity<*>) { if (!splitSelectStateController.isSplitSelectActive) { return } val anim = createPlaceholderDismissAnim(launcher) - anim.addListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - splitSelectStateController.resetState() - } - }) anim.start() } /** Returns [AnimatorSet] which slides initial split placeholder view offscreen. */ - fun createPlaceholderDismissAnim(launcher: Launcher) : AnimatorSet { + fun createPlaceholderDismissAnim(launcher: StatefulActivity<*>) : AnimatorSet { val animatorSet = AnimatorSet() val recentsView : RecentsView<*, *> = launcher.getOverviewPanel() val floatingTask: FloatingTaskView = splitSelectStateController.firstFloatingTaskView ?: return animatorSet // We are in split selection state currently, transitioning to another state - val dragLayer: DragLayer = launcher.dragLayer + val dragLayer: BaseDragLayer<*> = launcher.dragLayer val onScreenRectF = RectF() - Utilities.getBoundsForViewInDragLayer(launcher.dragLayer, floatingTask, + Utilities.getBoundsForViewInDragLayer(dragLayer, floatingTask, Rect(0, 0, floatingTask.width, floatingTask.height), false, null, onScreenRectF) // Get the part of the floatingTask that intersects with the DragLayer (i.e. the @@ -233,6 +232,42 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC floatingTask.stagePosition, launcher.deviceProfile ))) + animatorSet.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + splitSelectStateController.resetState() + safeRemoveViewFromDragLayer(launcher, splitInstructionsView) + } + }) return animatorSet } + + /** + * Returns a [PendingAnimation] to animate in the chip to instruct a user to select a second + * app for splitscreen + */ + fun getShowSplitInstructionsAnim(launcher: StatefulActivity<*>) : PendingAnimation { + safeRemoveViewFromDragLayer(launcher, splitInstructionsView) + splitInstructionsView = SplitInstructionsView.getSplitInstructionsView(launcher) + val timings = AnimUtils.getDeviceOverviewToSplitTimings(launcher.deviceProfile.isTablet) + val anim = PendingAnimation(100 /*duration */) + anim.setViewAlpha(splitInstructionsView, 1f, + Interpolators.clampToProgress(Interpolators.LINEAR, + timings.instructionsContainerFadeInStartOffset, + timings.instructionsContainerFadeInEndOffset)) + anim.setViewAlpha(splitInstructionsView!!.textView, 1f, + Interpolators.clampToProgress(Interpolators.LINEAR, + timings.instructionsTextFadeInStartOffset, + timings.instructionsTextFadeInEndOffset)) + anim.addFloat(splitInstructionsView, SplitInstructionsView.UNFOLD, 0.1f, 1f, + Interpolators.clampToProgress(Interpolators.EMPHASIZED_DECELERATE, + timings.instructionsUnfoldStartOffset, + timings.instructionsUnfoldEndOffset)) + return anim + } + + private fun safeRemoveViewFromDragLayer(launcher: StatefulActivity<*>, view: View?) { + if (view != null) { + launcher.dragLayer.removeView(view) + } + } } diff --git a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java index 0d9e41243b..4ca02e0a13 100644 --- a/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java +++ b/quickstep/src/com/android/quickstep/views/SplitInstructionsView.java @@ -17,15 +17,21 @@ package com.android.quickstep.views; import android.content.Context; +import android.graphics.drawable.Drawable; +import android.os.Bundle; import android.util.AttributeSet; import android.util.FloatProperty; +import android.view.MotionEvent; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatTextView; +import com.android.launcher3.LauncherState; import com.android.launcher3.R; +import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.statemanager.StatefulActivity; /** @@ -65,7 +71,7 @@ public class SplitInstructionsView extends FrameLayout { mLauncher = (StatefulActivity) context; } - static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) { + public static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) { ViewGroup dragLayer = launcher.getDragLayer(); final SplitInstructionsView splitInstructionsView = (SplitInstructionsView) launcher.getLayoutInflater().inflate( @@ -73,9 +79,7 @@ public class SplitInstructionsView extends FrameLayout { dragLayer, false ); - - splitInstructionsView.mTextView = splitInstructionsView.findViewById( - R.id.split_instructions_text); + splitInstructionsView.init(); // Since textview overlays base view, and we sometimes manipulate the alpha of each // simultaneously, force overlapping rendering to false prevents redrawing of pixels, @@ -92,6 +96,71 @@ public class SplitInstructionsView extends FrameLayout { ensureProperRotation(); } + private void init() { + mTextView = findViewById(R.id.split_instructions_text); + + if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) { + mTextView.setCompoundDrawables(null, null, null, null); + return; + } + + mTextView.setOnTouchListener((v, event) -> { + if (isTouchInsideRightCompoundDrawable(event)) { + if (event.getAction() == MotionEvent.ACTION_UP) { + exitSplitSelection(); + } + return true; + } + return false; + }); + } + + private void exitSplitSelection() { + ((RecentsView) mLauncher.getOverviewPanel()).getSplitSelectController() + .getSplitAnimationController().playPlaceholderDismissAnim(mLauncher); + mLauncher.getStateManager().goToState(LauncherState.NORMAL); + } + + private boolean isTouchInsideRightCompoundDrawable(MotionEvent event) { + // Get the right compound drawable of the TextView. + Drawable rightDrawable = mTextView.getCompoundDrawablesRelative()[2]; + + // Check if the touch event intersects with the drawable's bounds. + if (rightDrawable != null) { + // We can get away w/o caring about the Y bounds since it's such a small view, if it's + // above/below the drawable just assume they meant to touch it. ¯\_(ツ)_/¯ + return event.getX() >= (mTextView.getWidth() - rightDrawable.getBounds().width()); + } else { + return false; + } + } + + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) { + return; + } + + info.addAction(new AccessibilityNodeInfo.AccessibilityAction( + R.string.toast_split_select_cont_desc, + getResources().getString(R.string.toast_split_select_cont_desc) + )); + } + + @Override + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) { + return super.performAccessibilityAction(action, arguments); + } + + if (action == R.string.toast_split_select_cont_desc) { + exitSplitSelection(); + return true; + } + return super.performAccessibilityAction(action, arguments); + } + void ensureProperRotation() { ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler() .setSplitInstructionsParams( diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 6796d4b6f5..1079e0000d 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -411,6 +411,7 @@ 1dp 24dp 12dp + 10dp 24dp 60dp