1 /* 2 * Copyright (C) 2020 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 package com.android.quickstep.interaction; 17 18 import static com.android.launcher3.config.FeatureFlags.ENABLE_NEW_GESTURE_NAV_TUTORIAL; 19 20 import android.content.Context; 21 import android.content.res.TypedArray; 22 import android.graphics.Color; 23 import android.graphics.Insets; 24 import android.graphics.Rect; 25 import android.util.AttributeSet; 26 import android.view.MotionEvent; 27 import android.view.View; 28 import android.view.WindowInsets; 29 import android.widget.RelativeLayout; 30 31 import androidx.annotation.ColorInt; 32 import androidx.annotation.NonNull; 33 import androidx.annotation.Nullable; 34 import androidx.fragment.app.FragmentManager; 35 36 import com.android.launcher3.R; 37 import com.android.launcher3.Utilities; 38 39 /** Root layout that TutorialFragment uses to intercept motion events. */ 40 public class RootSandboxLayout extends RelativeLayout { 41 42 private final Rect mTempStepIndicatorBounds = new Rect(); 43 private final Rect mTempInclusionBounds = new Rect(); 44 private final Rect mTempExclusionBounds = new Rect(); 45 46 @ColorInt final int mColorSurfaceContainer; 47 @ColorInt final int mColorOnSurfaceHome; 48 @ColorInt final int mColorSurfaceHome; 49 @ColorInt final int mColorSecondaryHome; 50 @ColorInt final int mColorOnSurfaceBack; 51 @ColorInt final int mColorSurfaceBack; 52 @ColorInt final int mColorSecondaryBack; 53 @ColorInt final int mColorOnSurfaceOverview; 54 @ColorInt final int mColorSurfaceOverview; 55 @ColorInt final int mColorSecondaryOverview; 56 57 private View mFeedbackView; 58 private View mTutorialStepView; 59 private View mSkipButton; 60 private View mDoneButton; 61 RootSandboxLayout(Context context)62 public RootSandboxLayout(Context context) { 63 this(context, null); 64 } 65 RootSandboxLayout(Context context, AttributeSet attrs)66 public RootSandboxLayout(Context context, AttributeSet attrs) { 67 this(context, attrs, 0); 68 } 69 RootSandboxLayout(Context context, AttributeSet attrs, int defStyleAttr)70 public RootSandboxLayout(Context context, AttributeSet attrs, int defStyleAttr) { 71 this(context, attrs, defStyleAttr, 0); 72 } 73 RootSandboxLayout( @onNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes)74 public RootSandboxLayout( 75 @NonNull Context context, 76 @Nullable AttributeSet attrs, 77 int defStyleAttr, 78 int defStyleRes) { 79 super(context, attrs, defStyleAttr, defStyleRes); 80 TypedArray ta = context.obtainStyledAttributes( 81 attrs, 82 R.styleable.RootSandboxLayout, 83 defStyleAttr, 84 defStyleRes); 85 boolean isDarkTheme = Utilities.isDarkTheme(context); 86 int colorSurface = isDarkTheme ? Color.BLACK : Color.WHITE; 87 int colorOnSurface = isDarkTheme ? Color.WHITE : Color.BLACK; 88 int colorSecondary = Color.GRAY; 89 90 mColorSurfaceContainer = ta.getColor( 91 R.styleable.RootSandboxLayout_surfaceContainer, colorSurface); 92 mColorOnSurfaceHome = ta.getColor( 93 R.styleable.RootSandboxLayout_onSurfaceHome, colorOnSurface); 94 mColorSurfaceHome = ta.getColor(R.styleable.RootSandboxLayout_surfaceHome, colorSurface); 95 mColorSecondaryHome = ta.getColor( 96 R.styleable.RootSandboxLayout_secondaryHome, colorSecondary); 97 mColorOnSurfaceBack = ta.getColor( 98 R.styleable.RootSandboxLayout_onSurfaceBack, colorOnSurface); 99 mColorSurfaceBack = ta.getColor(R.styleable.RootSandboxLayout_surfaceBack, colorSurface); 100 mColorSecondaryBack = ta.getColor( 101 R.styleable.RootSandboxLayout_secondaryBack, colorSecondary); 102 mColorOnSurfaceOverview = ta.getColor( 103 R.styleable.RootSandboxLayout_onSurfaceOverview, colorOnSurface); 104 mColorSurfaceOverview = ta.getColor( 105 R.styleable.RootSandboxLayout_surfaceOverview, colorSurface); 106 mColorSecondaryOverview = ta.getColor( 107 R.styleable.RootSandboxLayout_secondaryOverview, colorSecondary); 108 109 ta.recycle(); 110 } 111 112 @Override onInterceptTouchEvent(MotionEvent motionEvent)113 public boolean onInterceptTouchEvent(MotionEvent motionEvent) { 114 return ((TutorialFragment) FragmentManager.findFragment(this)) 115 .onInterceptTouch(motionEvent); 116 } 117 118 /** 119 * Returns this view's fullscreen height. This method is agnostic of this view's actual height. 120 */ getFullscreenHeight()121 public int getFullscreenHeight() { 122 Insets insets = getRootWindowInsets().getInsets(WindowInsets.Type.systemBars()); 123 124 return getHeight() + insets.top + insets.bottom; 125 } 126 127 @Override onFinishInflate()128 protected void onFinishInflate() { 129 super.onFinishInflate(); 130 if (ENABLE_NEW_GESTURE_NAV_TUTORIAL.get()) { 131 return; 132 } 133 mFeedbackView = findViewById(R.id.gesture_tutorial_fragment_feedback_view); 134 mTutorialStepView = 135 mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_feedback_tutorial_step); 136 mSkipButton = mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_close_button); 137 mDoneButton = mFeedbackView.findViewById(R.id.gesture_tutorial_fragment_action_button); 138 139 mFeedbackView.addOnLayoutChangeListener( 140 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 141 if (mSkipButton.getVisibility() != VISIBLE 142 && mDoneButton.getVisibility() != VISIBLE) { 143 return; 144 } 145 // Either the skip or the done button is ever shown at once, never both. 146 boolean showingSkipButton = mSkipButton.getVisibility() == VISIBLE; 147 boolean isRTL = Utilities.isRtl(getContext().getResources()); 148 updateTutorialStepViewTranslation( 149 showingSkipButton ? mSkipButton : mDoneButton, 150 // Translate the step indicator away from whichever button is being 151 // shown. The skip button in on the left in LTR or on the right in RTL. 152 // The done button is on the right in LTR or left in RTL. 153 (showingSkipButton && !isRTL) || (!showingSkipButton && isRTL)); 154 }); 155 } 156 updateTutorialStepViewTranslation( @onNull View anchorView, boolean translateToRight)157 private void updateTutorialStepViewTranslation( 158 @NonNull View anchorView, boolean translateToRight) { 159 mTempStepIndicatorBounds.set( 160 mTutorialStepView.getLeft(), 161 mTutorialStepView.getTop(), 162 mTutorialStepView.getRight(), 163 mTutorialStepView.getBottom()); 164 mTempInclusionBounds.set(0, 0, mFeedbackView.getWidth(), mFeedbackView.getHeight()); 165 mTempExclusionBounds.set( 166 anchorView.getLeft(), 167 anchorView.getTop(), 168 anchorView.getRight(), 169 anchorView.getBottom()); 170 171 Utilities.translateOverlappingView( 172 mTutorialStepView, 173 mTempStepIndicatorBounds, 174 mTempInclusionBounds, 175 mTempExclusionBounds, 176 translateToRight ? Utilities.TRANSLATE_RIGHT : Utilities.TRANSLATE_LEFT); 177 } 178 } 179