Loading quickstep/res/layout/split_instructions_view.xml +4 −1 Original line number Diff line number Diff line Loading @@ -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"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/split_instructions_text" android:layout_height="wrap_content" android:layout_width="wrap_content" android:gravity="center" android:textColor="?androidprv:attr/textColorOnAccent" android:drawableEnd="@drawable/ic_split_horizontal" android:drawablePadding="@dimen/split_instructions_drawable_padding" android:text="@string/toast_split_select_app" /> </com.android.quickstep.views.SplitInstructionsView> No newline at end of file quickstep/res/values/strings.xml +1 −0 Original line number Diff line number Diff line Loading @@ -230,6 +230,7 @@ <string name="action_split">Split</string> <!-- Label for toast with instructions for split screen selection mode. [CHAR_LIMIT=50] --> <string name="toast_split_select_app">Tap another app to use split screen</string> <string name="toast_split_select_cont_desc">Exit split screen selection</string> <!-- Label for toast when app selected for split isn't supported. [CHAR_LIMIT=50] --> <string name="toast_split_app_unsupported">Choose another app to use split screen</string> <!-- Message shown when an action is blocked by a policy enforced by the app or the organization managing the device. [CHAR_LIMIT=NONE] --> Loading quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +5 −11 Original line number Diff line number Diff line Loading @@ -544,17 +544,9 @@ public class QuickstepLauncher extends Launcher { ArrayList<TouchController> list = new ArrayList<>(); list.add(getDragController()); Consumer<AnimatorSet> 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<AnimatorSet> splitAnimator = animatorSet -> animatorSet.play(mSplitSelectStateController.getSplitAnimationController() .createPlaceholderDismissAnim(this)); switch (mode) { case NO_BUTTON: list.add(new NoButtonQuickSwitchTouchController(this)); Loading Loading @@ -673,6 +665,8 @@ public class QuickstepLauncher extends Launcher { mSplitSelectStateController.resetState(); } }); anim.add(mSplitSelectStateController.getSplitAnimationController() .getShowSplitInstructionsAnim(this).buildAnim()); anim.buildAnim().start(); } Loading quickstep/src/com/android/quickstep/util/SplitAnimationController.kt +46 −11 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading @@ -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) } } } quickstep/src/com/android/quickstep/views/SplitInstructionsView.java +73 −4 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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( Loading @@ -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, Loading @@ -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( Loading Loading
quickstep/res/layout/split_instructions_view.xml +4 −1 Original line number Diff line number Diff line Loading @@ -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"> <androidx.appcompat.widget.AppCompatTextView android:id="@+id/split_instructions_text" android:layout_height="wrap_content" android:layout_width="wrap_content" android:gravity="center" android:textColor="?androidprv:attr/textColorOnAccent" android:drawableEnd="@drawable/ic_split_horizontal" android:drawablePadding="@dimen/split_instructions_drawable_padding" android:text="@string/toast_split_select_app" /> </com.android.quickstep.views.SplitInstructionsView> No newline at end of file
quickstep/res/values/strings.xml +1 −0 Original line number Diff line number Diff line Loading @@ -230,6 +230,7 @@ <string name="action_split">Split</string> <!-- Label for toast with instructions for split screen selection mode. [CHAR_LIMIT=50] --> <string name="toast_split_select_app">Tap another app to use split screen</string> <string name="toast_split_select_cont_desc">Exit split screen selection</string> <!-- Label for toast when app selected for split isn't supported. [CHAR_LIMIT=50] --> <string name="toast_split_app_unsupported">Choose another app to use split screen</string> <!-- Message shown when an action is blocked by a policy enforced by the app or the organization managing the device. [CHAR_LIMIT=NONE] --> Loading
quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +5 −11 Original line number Diff line number Diff line Loading @@ -544,17 +544,9 @@ public class QuickstepLauncher extends Launcher { ArrayList<TouchController> list = new ArrayList<>(); list.add(getDragController()); Consumer<AnimatorSet> 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<AnimatorSet> splitAnimator = animatorSet -> animatorSet.play(mSplitSelectStateController.getSplitAnimationController() .createPlaceholderDismissAnim(this)); switch (mode) { case NO_BUTTON: list.add(new NoButtonQuickSwitchTouchController(this)); Loading Loading @@ -673,6 +665,8 @@ public class QuickstepLauncher extends Launcher { mSplitSelectStateController.resetState(); } }); anim.add(mSplitSelectStateController.getSplitAnimationController() .getShowSplitInstructionsAnim(this).buildAnim()); anim.buildAnim().start(); } Loading
quickstep/src/com/android/quickstep/util/SplitAnimationController.kt +46 −11 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading Loading @@ -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 Loading @@ -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) } } }
quickstep/src/com/android/quickstep/views/SplitInstructionsView.java +73 −4 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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( Loading @@ -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, Loading @@ -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( Loading