• 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 android.content.Context;
20 import android.graphics.drawable.Drawable;
21 import android.os.Bundle;
22 import android.util.AttributeSet;
23 import android.util.FloatProperty;
24 import android.view.MotionEvent;
25 import android.view.ViewGroup;
26 import android.view.accessibility.AccessibilityNodeInfo;
27 import android.widget.FrameLayout;
28 
29 import androidx.annotation.Nullable;
30 import androidx.appcompat.widget.AppCompatTextView;
31 
32 import com.android.launcher3.LauncherState;
33 import com.android.launcher3.R;
34 import com.android.launcher3.config.FeatureFlags;
35 import com.android.launcher3.statemanager.StatefulActivity;
36 
37 /**
38  * A rounded rectangular component containing a single TextView.
39  * Appears when a split is in progress, and tells the user to select a second app to initiate
40  * splitscreen.
41  *
42  * Appears and disappears concurrently with a FloatingTaskView.
43  */
44 public class SplitInstructionsView extends FrameLayout {
45     private final StatefulActivity mLauncher;
46     private AppCompatTextView mTextView;
47 
48     public static final FloatProperty<SplitInstructionsView> UNFOLD =
49             new FloatProperty<SplitInstructionsView>("SplitInstructionsUnfold") {
50                 @Override
51                 public void setValue(SplitInstructionsView splitInstructionsView, float v) {
52                     splitInstructionsView.setScaleY(v);
53                 }
54 
55                 @Override
56                 public Float get(SplitInstructionsView splitInstructionsView) {
57                     return splitInstructionsView.getScaleY();
58                 }
59             };
60 
SplitInstructionsView(Context context)61     public SplitInstructionsView(Context context) {
62         this(context, null);
63     }
64 
SplitInstructionsView(Context context, @Nullable AttributeSet attrs)65     public SplitInstructionsView(Context context, @Nullable AttributeSet attrs) {
66         this(context, attrs, 0);
67     }
68 
SplitInstructionsView(Context context, AttributeSet attrs, int defStyleAttr)69     public SplitInstructionsView(Context context, AttributeSet attrs, int defStyleAttr) {
70         super(context, attrs, defStyleAttr);
71         mLauncher = (StatefulActivity) context;
72     }
73 
getSplitInstructionsView(StatefulActivity launcher)74     public static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) {
75         ViewGroup dragLayer = launcher.getDragLayer();
76         final SplitInstructionsView splitInstructionsView =
77                 (SplitInstructionsView) launcher.getLayoutInflater().inflate(
78                         R.layout.split_instructions_view,
79                         dragLayer,
80                         false
81                 );
82         splitInstructionsView.init();
83 
84         // Since textview overlays base view, and we sometimes manipulate the alpha of each
85         // simultaneously, force overlapping rendering to false prevents redrawing of pixels,
86         // improving performance at the cost of some accuracy.
87         splitInstructionsView.forceHasOverlappingRendering(false);
88 
89         dragLayer.addView(splitInstructionsView);
90         return splitInstructionsView;
91     }
92 
93     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)94     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
95         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
96         ensureProperRotation();
97     }
98 
init()99     private void init() {
100         mTextView = findViewById(R.id.split_instructions_text);
101 
102         if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
103             mTextView.setCompoundDrawables(null, null, null, null);
104             return;
105         }
106 
107         mTextView.setOnTouchListener((v, event) -> {
108             if (isTouchInsideRightCompoundDrawable(event)) {
109                 if (event.getAction() == MotionEvent.ACTION_UP) {
110                     exitSplitSelection();
111                 }
112                 return true;
113             }
114             return false;
115         });
116     }
117 
exitSplitSelection()118     private void exitSplitSelection() {
119         ((RecentsView) mLauncher.getOverviewPanel()).getSplitSelectController()
120                 .getSplitAnimationController().playPlaceholderDismissAnim(mLauncher);
121         mLauncher.getStateManager().goToState(LauncherState.NORMAL);
122     }
123 
isTouchInsideRightCompoundDrawable(MotionEvent event)124     private boolean isTouchInsideRightCompoundDrawable(MotionEvent event) {
125         // Get the right compound drawable of the TextView.
126         Drawable rightDrawable = mTextView.getCompoundDrawablesRelative()[2];
127 
128         // Check if the touch event intersects with the drawable's bounds.
129         if (rightDrawable != null) {
130             // We can get away w/o caring about the Y bounds since it's such a small view, if it's
131             // above/below the drawable just assume they meant to touch it. ¯\_(ツ)_/¯
132             return  event.getX() >= (mTextView.getWidth() - rightDrawable.getBounds().width());
133         } else {
134             return false;
135         }
136     }
137 
138     @Override
onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)139     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
140         super.onInitializeAccessibilityNodeInfo(info);
141         if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
142             return;
143         }
144 
145         info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
146                 R.string.toast_split_select_cont_desc,
147                 getResources().getString(R.string.toast_split_select_cont_desc)
148         ));
149     }
150 
151     @Override
performAccessibilityAction(int action, Bundle arguments)152     public boolean performAccessibilityAction(int action, Bundle arguments) {
153         if (!FeatureFlags.ENABLE_SPLIT_FROM_WORKSPACE_TO_WORKSPACE.get()) {
154             return super.performAccessibilityAction(action, arguments);
155         }
156 
157         if (action == R.string.toast_split_select_cont_desc) {
158             exitSplitSelection();
159             return true;
160         }
161         return super.performAccessibilityAction(action, arguments);
162     }
163 
ensureProperRotation()164     void ensureProperRotation() {
165         ((RecentsView) mLauncher.getOverviewPanel()).getPagedOrientationHandler()
166                 .setSplitInstructionsParams(
167                         this,
168                         mLauncher.getDeviceProfile(),
169                         getMeasuredHeight(),
170                         getMeasuredWidth()
171                 );
172     }
173 
getTextView()174     public AppCompatTextView getTextView() {
175         return mTextView;
176     }
177 }
178