Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5f090915 authored by Vinit Nayak's avatar Vinit Nayak
Browse files

Show split select instructions toast when starting split on workspace

* Used same animation properties that RecentsView uses to
show the instructions view unfolding
* TODOs:
  * UI polish (correct asset, animation timings, etc)
  * Hook into existing animations (if possible)
  * Migrate recentsView splitInstructionsView to use same/similar code path;
    remove RecentsView#safeRemoveDragLayerView

Bug: 276361926
Test: Create split w/ and w/o flag, works as expected
Flag: ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE
Change-Id: I7a51b71c358902399ad73b650287cdbf1dad07c6
parent 3396b5c3
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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] -->
+5 −11
Original line number Diff line number Diff line
@@ -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));
@@ -673,6 +665,8 @@ public class QuickstepLauncher extends Launcher {
                mSplitSelectStateController.resetState();
            }
        });
        anim.add(mSplitSelectStateController.getSplitAnimationController()
                .getShowSplitInstructionsAnim(this).buildAnim());
        anim.buildAnim().start();
    }

+46 −11
Original line number Diff line number Diff line
@@ -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)
        }
    }
}
+73 −4
Original line number Diff line number Diff line
@@ -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(
Loading