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