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