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 android.content.Context; 19 import android.graphics.drawable.RippleDrawable; 20 import android.view.View; 21 import android.view.View.OnClickListener; 22 import android.widget.Button; 23 import android.widget.ImageButton; 24 import android.widget.ImageView; 25 import android.widget.TextView; 26 27 import androidx.annotation.CallSuper; 28 import androidx.annotation.Nullable; 29 30 import com.android.launcher3.R; 31 import com.android.launcher3.views.ClipIconView; 32 import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback; 33 import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback; 34 35 abstract class TutorialController implements BackGestureAttemptCallback, 36 NavBarGestureAttemptCallback { 37 38 private static final int FEEDBACK_VISIBLE_MS = 3000; 39 private static final int FEEDBACK_ANIMATION_MS = 500; 40 private static final int RIPPLE_VISIBLE_MS = 300; 41 42 final TutorialFragment mTutorialFragment; 43 TutorialType mTutorialType; 44 final Context mContext; 45 46 final ImageButton mCloseButton; 47 final TextView mTitleTextView; 48 final TextView mSubtitleTextView; 49 final TextView mFeedbackView; 50 final ClipIconView mFakeIconView; 51 final View mFakeTaskView; 52 final View mRippleView; 53 final RippleDrawable mRippleDrawable; 54 final TutorialHandAnimation mHandCoachingAnimation; 55 final ImageView mHandCoachingView; 56 final Button mActionTextButton; 57 final Button mActionButton; 58 private final Runnable mHideFeedbackRunnable; 59 TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType)60 TutorialController(TutorialFragment tutorialFragment, TutorialType tutorialType) { 61 mTutorialFragment = tutorialFragment; 62 mTutorialType = tutorialType; 63 mContext = mTutorialFragment.getContext(); 64 65 View rootView = tutorialFragment.getRootView(); 66 mCloseButton = rootView.findViewById(R.id.gesture_tutorial_fragment_close_button); 67 mCloseButton.setOnClickListener(button -> mTutorialFragment.closeTutorial()); 68 mTitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_title_view); 69 mSubtitleTextView = rootView.findViewById(R.id.gesture_tutorial_fragment_subtitle_view); 70 mFeedbackView = rootView.findViewById(R.id.gesture_tutorial_fragment_feedback_view); 71 mFakeIconView = rootView.findViewById(R.id.gesture_tutorial_fake_icon_view); 72 mFakeTaskView = rootView.findViewById(R.id.gesture_tutorial_fake_task_view); 73 mRippleView = rootView.findViewById(R.id.gesture_tutorial_ripple_view); 74 mRippleDrawable = (RippleDrawable) mRippleView.getBackground(); 75 mHandCoachingAnimation = tutorialFragment.getHandAnimation(); 76 mHandCoachingView = rootView.findViewById(R.id.gesture_tutorial_fragment_hand_coaching); 77 mHandCoachingView.bringToFront(); 78 mActionTextButton = 79 rootView.findViewById(R.id.gesture_tutorial_fragment_action_text_button); 80 mActionButton = rootView.findViewById(R.id.gesture_tutorial_fragment_action_button); 81 82 mHideFeedbackRunnable = 83 () -> mFeedbackView.animate().alpha(0).setDuration(FEEDBACK_ANIMATION_MS) 84 .withEndAction(this::showHandCoachingAnimation).start(); 85 } 86 setTutorialType(TutorialType tutorialType)87 void setTutorialType(TutorialType tutorialType) { 88 mTutorialType = tutorialType; 89 } 90 91 @Nullable getTitleStringId()92 Integer getTitleStringId() { 93 return null; 94 } 95 96 @Nullable getSubtitleStringId()97 Integer getSubtitleStringId() { 98 return null; 99 } 100 101 @Nullable getActionButtonStringId()102 Integer getActionButtonStringId() { 103 return null; 104 } 105 106 @Nullable getActionTextButtonStringId()107 Integer getActionTextButtonStringId() { 108 return null; 109 } 110 showFeedback(int resId)111 void showFeedback(int resId) { 112 hideHandCoachingAnimation(); 113 mFeedbackView.setText(resId); 114 mFeedbackView.animate().alpha(1).setDuration(FEEDBACK_ANIMATION_MS).start(); 115 mFeedbackView.removeCallbacks(mHideFeedbackRunnable); 116 mFeedbackView.postDelayed(mHideFeedbackRunnable, FEEDBACK_VISIBLE_MS); 117 } 118 hideFeedback()119 void hideFeedback() { 120 mFeedbackView.setText(null); 121 mFeedbackView.removeCallbacks(mHideFeedbackRunnable); 122 mFeedbackView.clearAnimation(); 123 mFeedbackView.setAlpha(0); 124 } 125 setRippleHotspot(float x, float y)126 void setRippleHotspot(float x, float y) { 127 mRippleDrawable.setHotspot(x, y); 128 } 129 showRippleEffect(@ullable Runnable onCompleteRunnable)130 void showRippleEffect(@Nullable Runnable onCompleteRunnable) { 131 mRippleDrawable.setState( 132 new int[] {android.R.attr.state_pressed, android.R.attr.state_enabled}); 133 mRippleView.postDelayed(() -> { 134 mRippleDrawable.setState(new int[] {}); 135 if (onCompleteRunnable != null) { 136 onCompleteRunnable.run(); 137 } 138 }, RIPPLE_VISIBLE_MS); 139 } 140 onActionButtonClicked(View button)141 void onActionButtonClicked(View button) {} 142 onActionTextButtonClicked(View button)143 void onActionTextButtonClicked(View button) {} 144 showHandCoachingAnimation()145 void showHandCoachingAnimation() { 146 if (isComplete()) { 147 return; 148 } 149 mHandCoachingAnimation.startLoopedAnimation(mTutorialType); 150 } 151 hideHandCoachingAnimation()152 void hideHandCoachingAnimation() { 153 mHandCoachingAnimation.stop(); 154 mHandCoachingView.setVisibility(View.INVISIBLE); 155 } 156 157 @CallSuper transitToController()158 void transitToController() { 159 hideFeedback(); 160 updateTitles(); 161 updateActionButtons(); 162 163 if (isComplete()) { 164 hideHandCoachingAnimation(); 165 } else { 166 showHandCoachingAnimation(); 167 } 168 } 169 updateTitles()170 private void updateTitles() { 171 updateTitleView(mTitleTextView, getTitleStringId(), 172 R.style.TextAppearance_GestureTutorial_Title); 173 updateTitleView(mSubtitleTextView, getSubtitleStringId(), 174 R.style.TextAppearance_GestureTutorial_Subtitle); 175 } 176 updateTitleView(TextView textView, @Nullable Integer stringId, int styleId)177 private void updateTitleView(TextView textView, @Nullable Integer stringId, int styleId) { 178 if (stringId == null) { 179 textView.setVisibility(View.GONE); 180 return; 181 } 182 183 textView.setVisibility(View.VISIBLE); 184 textView.setText(stringId); 185 textView.setTextAppearance(styleId); 186 } 187 updateActionButtons()188 private void updateActionButtons() { 189 updateButton(mActionButton, getActionButtonStringId(), this::onActionButtonClicked); 190 updateButton( 191 mActionTextButton, getActionTextButtonStringId(), this::onActionTextButtonClicked); 192 } 193 updateButton(Button button, @Nullable Integer stringId, OnClickListener listener)194 private void updateButton(Button button, @Nullable Integer stringId, OnClickListener listener) { 195 if (stringId == null) { 196 button.setVisibility(View.INVISIBLE); 197 return; 198 } 199 200 button.setVisibility(View.VISIBLE); 201 button.setText(stringId); 202 button.setOnClickListener(listener); 203 } 204 isComplete()205 private boolean isComplete() { 206 return mTutorialType == TutorialType.BACK_NAVIGATION_COMPLETE 207 || mTutorialType == TutorialType.HOME_NAVIGATION_COMPLETE 208 || mTutorialType == TutorialType.OVERVIEW_NAVIGATION_COMPLETE 209 || mTutorialType == TutorialType.ASSISTANT_COMPLETE; 210 } 211 212 /** Denotes the type of the tutorial. */ 213 enum TutorialType { 214 RIGHT_EDGE_BACK_NAVIGATION, 215 LEFT_EDGE_BACK_NAVIGATION, 216 BACK_NAVIGATION_COMPLETE, 217 HOME_NAVIGATION, 218 HOME_NAVIGATION_COMPLETE, 219 OVERVIEW_NAVIGATION, 220 OVERVIEW_NAVIGATION_COMPLETE, 221 ASSISTANT, 222 ASSISTANT_COMPLETE 223 } 224 } 225