• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.quickstep.views;
18 
19 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON;
20 import static com.android.settingslib.widget.theme.R.dimen.settingslib_preferred_minimum_touch_target;
21 
22 import android.animation.Animator;
23 import android.animation.AnimatorListenerAdapter;
24 import android.content.Context;
25 import android.graphics.Rect;
26 import android.util.AttributeSet;
27 import android.util.FloatProperty;
28 import android.view.TouchDelegate;
29 import android.view.ViewGroup;
30 import android.widget.LinearLayout;
31 import android.widget.TextView;
32 
33 import androidx.annotation.Nullable;
34 import androidx.dynamicanimation.animation.DynamicAnimation;
35 import androidx.dynamicanimation.animation.SpringAnimation;
36 import androidx.dynamicanimation.animation.SpringForce;
37 
38 import com.android.app.animation.Interpolators;
39 import com.android.launcher3.R;
40 import com.android.launcher3.Utilities;
41 import com.android.launcher3.anim.PendingAnimation;
42 import com.android.launcher3.statemanager.StateManager;
43 import com.android.quickstep.util.AnimUtils;
44 import com.android.quickstep.util.SplitSelectStateController;
45 import com.android.wm.shell.shared.TypefaceUtils;
46 import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
47 
48 /**
49  * A rounded rectangular component containing a single TextView.
50  * Appears when a split is in progress, and tells the user to select a second app to initiate
51  * splitscreen.
52  *
53  * Appears and disappears concurrently with a FloatingTaskView.
54  */
55 public class SplitInstructionsView extends LinearLayout {
56     private static final int BOUNCE_DURATION = 250;
57     private static final float BOUNCE_HEIGHT = 20;
58 
59     private final RecentsViewContainer mContainer;
60     public boolean mIsCurrentlyAnimating = false;
61 
62     public static final FloatProperty<SplitInstructionsView> UNFOLD =
63             new FloatProperty<>("SplitInstructionsUnfold") {
64                 @Override
65                 public void setValue(SplitInstructionsView splitInstructionsView, float v) {
66                     splitInstructionsView.setScaleY(v);
67                 }
68 
69                 @Override
70                 public Float get(SplitInstructionsView splitInstructionsView) {
71                     return splitInstructionsView.getScaleY();
72                 }
73             };
74 
75     public static final FloatProperty<SplitInstructionsView> TRANSLATE_Y =
76             new FloatProperty<>("SplitInstructionsTranslateY") {
77                 @Override
78                 public void setValue(SplitInstructionsView splitInstructionsView, float v) {
79                     splitInstructionsView.setTranslationY(v);
80                 }
81 
82                 @Override
83                 public Float get(SplitInstructionsView splitInstructionsView) {
84                     return splitInstructionsView.getTranslationY();
85                 }
86             };
87 
SplitInstructionsView(Context context)88     public SplitInstructionsView(Context context) {
89         this(context, null);
90     }
91 
SplitInstructionsView(Context context, @Nullable AttributeSet attrs)92     public SplitInstructionsView(Context context, @Nullable AttributeSet attrs) {
93         this(context, attrs, 0);
94     }
95 
SplitInstructionsView(Context context, AttributeSet attrs, int defStyleAttr)96     public SplitInstructionsView(Context context, AttributeSet attrs, int defStyleAttr) {
97         super(context, attrs, defStyleAttr);
98         mContainer = RecentsViewContainer.containerFromContext(context);
99     }
100 
getSplitInstructionsView(RecentsViewContainer container)101     public static SplitInstructionsView getSplitInstructionsView(RecentsViewContainer container) {
102         ViewGroup dragLayer = container.getDragLayer();
103         final SplitInstructionsView splitInstructionsView =
104                 (SplitInstructionsView) container.getLayoutInflater().inflate(
105                         R.layout.split_instructions_view,
106                         dragLayer,
107                         false
108                 );
109         splitInstructionsView.init();
110 
111         // Since textview overlays base view, and we sometimes manipulate the alpha of each
112         // simultaneously, force overlapping rendering to false prevents redrawing of pixels,
113         // improving performance at the cost of some accuracy.
114         splitInstructionsView.forceHasOverlappingRendering(false);
115 
116         dragLayer.addView(splitInstructionsView);
117         return splitInstructionsView;
118     }
119 
120     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)121     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
122         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
123         ensureProperRotation();
124     }
125 
init()126     private void init() {
127         TextView cancelTextView = findViewById(R.id.split_instructions_text_cancel);
128         TextView instructionTextView = findViewById(R.id.split_instructions_text);
129 
130         cancelTextView.setVisibility(VISIBLE);
131         cancelTextView.setOnClickListener((v) -> exitSplitSelection());
132         instructionTextView.setText(R.string.toast_contextual_split_select_app);
133         TypefaceUtils.setTypeface(instructionTextView, FontFamily.GSF_BODY_MEDIUM);
134 
135         // After layout, expand touch target of cancel button to meet minimum a11y measurements.
136         post(() -> {
137             int minTouchSize = getResources()
138                     .getDimensionPixelSize(settingslib_preferred_minimum_touch_target);
139             Rect r = new Rect();
140             cancelTextView.getHitRect(r);
141 
142             if (r.width() < minTouchSize) {
143                 // add 1 to ensure ceiling on int division
144                 int expandAmount = (minTouchSize + 1 - r.width()) / 2;
145                 r.left -= expandAmount;
146                 r.right += expandAmount;
147             }
148             if (r.height() < minTouchSize) {
149                 int expandAmount = (minTouchSize + 1 - r.height()) / 2;
150                 r.top -= expandAmount;
151                 r.bottom += expandAmount;
152             }
153 
154             setTouchDelegate(new TouchDelegate(r, cancelTextView));
155         });
156 
157         // Set accessibility title, will be announced by a11y tools.
158         instructionTextView.setAccessibilityPaneTitle(instructionTextView.getText());
159     }
160 
exitSplitSelection()161     private void exitSplitSelection() {
162         RecentsView recentsView = mContainer.getOverviewPanel();
163         SplitSelectStateController splitSelectController = recentsView.getSplitSelectController();
164         StateManager stateManager = recentsView.getStateManager();
165 
166         AnimUtils.goToNormalStateWithSplitDismissal(stateManager, mContainer,
167                 LAUNCHER_SPLIT_SELECTION_EXIT_CANCEL_BUTTON,
168                 splitSelectController.getSplitAnimationController());
169     }
170 
ensureProperRotation()171     void ensureProperRotation() {
172         ((RecentsView) mContainer.getOverviewPanel()).getPagedOrientationHandler()
173                 .setSplitInstructionsParams(
174                         this,
175                         mContainer.getDeviceProfile(),
176                         getMeasuredHeight(),
177                         getMeasuredWidth()
178                 );
179     }
180 
181     /**
182      * Draws attention to the split instructions view by bouncing it up and down.
183      */
goBoing()184     public void goBoing() {
185         if (mIsCurrentlyAnimating) {
186             return;
187         }
188 
189         float restingY = getTranslationY();
190         float bounceToY = restingY - Utilities.dpToPx(BOUNCE_HEIGHT);
191         PendingAnimation anim = new PendingAnimation(BOUNCE_DURATION);
192         // Animate the view lifting up to a higher position
193         anim.addFloat(this, TRANSLATE_Y, restingY, bounceToY, Interpolators.STANDARD);
194 
195         anim.addListener(new AnimatorListenerAdapter() {
196             @Override
197             public void onAnimationStart(Animator animation) {
198                 mIsCurrentlyAnimating = true;
199             }
200 
201             @Override
202             public void onAnimationEnd(Animator animation) {
203                 // Create a low stiffness, medium bounce spring centering at the rest position
204                 SpringForce spring = new SpringForce(restingY)
205                         .setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
206                         .setStiffness(SpringForce.STIFFNESS_LOW);
207                 // Animate the view getting pulled back to rest position by the spring
208                 SpringAnimation springAnim = new SpringAnimation(SplitInstructionsView.this,
209                         DynamicAnimation.TRANSLATION_Y).setSpring(spring).setStartValue(bounceToY);
210 
211                 springAnim.addEndListener((a, b, c, d) -> mIsCurrentlyAnimating = false);
212                 springAnim.start();
213             }
214         });
215 
216         anim.buildAnim().start();
217     }
218 }
219