blob: 4ca02e0a134b82f23b6111992fbb82b4c30c7b96 [file] [log] [blame]
/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
/**
* A rounded rectangular component containing a single TextView.
* Appears when a split is in progress, and tells the user to select a second app to initiate
* splitscreen.
*
* Appears and disappears concurrently with a FloatingTaskView.
*/
public class SplitInstructionsView extends FrameLayout {
private final StatefulActivity mLauncher;
private AppCompatTextView mTextView;
public static final FloatProperty<SplitInstructionsView> UNFOLD =
new FloatProperty<SplitInstructionsView>("SplitInstructionsUnfold") {
@Override
public void setValue(SplitInstructionsView splitInstructionsView, float v) {
splitInstructionsView.setScaleY(v);
}
@Override
public Float get(SplitInstructionsView splitInstructionsView) {
return splitInstructionsView.getScaleY();
}
};
public SplitInstructionsView(Context context) {
this(context, null);
}
public SplitInstructionsView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public SplitInstructionsView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLauncher = (StatefulActivity) context;
}
public static SplitInstructionsView getSplitInstructionsView(StatefulActivity launcher) {
ViewGroup dragLayer = launcher.getDragLayer();
final SplitInstructionsView splitInstructionsView =
(SplitInstructionsView) launcher.getLayoutInflater().inflate(
R.layout.split_instructions_view,
dragLayer,
false
);
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,
// improving performance at the cost of some accuracy.
splitInstructionsView.forceHasOverlappingRendering(false);
dragLayer.addView(splitInstructionsView);
return splitInstructionsView;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
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(
this,
mLauncher.getDeviceProfile(),
getMeasuredHeight(),
getMeasuredWidth()
);
}
public AppCompatTextView getTextView() {
return mTextView;
}
}