From 72f9375adafd50ef45e3ad4177e0fab1cd61ca69 Mon Sep 17 00:00:00 2001 From: Pinyao Ting Date: Thu, 26 Dec 2019 10:22:00 -0800 Subject: [PATCH] Tips Gesture Navigation Tutorial [Part 2] include actual implementation of gesture tutorial. Bug: 146173041 Test: adb shell am start -a \ "com.android.quickstep.action.BACK_GESTURE_TUTORIAL" Change-Id: Ic166f0a10d8efc22d9684f089142de164ca24c90 --- quickstep/AndroidManifest.xml | 11 ++ quickstep/res/drawable/ic_bulb_outline.xml | 29 --- quickstep/res/values/strings.xml | 7 - quickstep/res/xml/notification_action.xml | 19 -- .../BackGestureTutorialActivity.java | 73 ++++++++ .../BackGestureTutorialConfirmController.java | 64 +++++++ .../BackGestureTutorialController.java | 165 ++++++++++++++++++ .../BackGestureTutorialEngagedController.java | 64 +++++++ .../BackGestureTutorialFragment.java | 165 ++++++++++++++++++ .../BackGestureTutorialHandAnimation.java | 94 ++++++++++ .../BackGestureTutorialTypeInfo.java | 109 ++++++++++++ .../BackGestureTutorialTypeInfoProvider.java | 59 +++++++ 12 files changed, 804 insertions(+), 55 deletions(-) delete mode 100644 quickstep/res/drawable/ic_bulb_outline.xml delete mode 100644 quickstep/res/xml/notification_action.xml create mode 100644 quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java create mode 100644 quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java create mode 100644 quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java create mode 100644 quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java create mode 100644 quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java create mode 100644 quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java create mode 100644 quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java create mode 100644 quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml index 826a27553c..5d871c363f 100644 --- a/quickstep/AndroidManifest.xml +++ b/quickstep/AndroidManifest.xml @@ -91,6 +91,17 @@ android:taskAffinity="${packageName}.locktask" android:directBootAware="true" /> + + + + + + + diff --git a/quickstep/res/drawable/ic_bulb_outline.xml b/quickstep/res/drawable/ic_bulb_outline.xml deleted file mode 100644 index ef7bf9a07e..0000000000 --- a/quickstep/res/drawable/ic_bulb_outline.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - \ No newline at end of file diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml index 378858ffcb..2bc5015a26 100644 --- a/quickstep/res/values/strings.xml +++ b/quickstep/res/values/strings.xml @@ -69,13 +69,6 @@ Close - - Try the new back gesture - - Learn how to go back while using your apps - - Try it - Try the back gesture diff --git a/quickstep/res/xml/notification_action.xml b/quickstep/res/xml/notification_action.xml deleted file mode 100644 index cc3612e2d8..0000000000 --- a/quickstep/res/xml/notification_action.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - \ No newline at end of file diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java new file mode 100644 index 0000000000..295ab4897e --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialActivity.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 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.interaction; + +import android.graphics.Color; +import android.os.Bundle; +import android.view.View; +import android.view.Window; + +import androidx.fragment.app.FragmentActivity; + +import com.android.launcher3.R; +import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep; +import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType; + +import java.util.Optional; + +/** Shows the Back gesture interactive tutorial in full screen mode. */ +public class BackGestureTutorialActivity extends FragmentActivity { + + Optional mFragment = Optional.empty(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.back_gesture_tutorial_activity); + + mFragment = Optional.of(BackGestureTutorialFragment.newInstance(TutorialStep.ENGAGED, + TutorialType.RIGHT_EDGE_BACK_NAVIGATION)); + getSupportFragmentManager().beginTransaction() + .add(R.id.back_gesture_tutorial_fragment_container, mFragment.get()) + .commit(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + hideSystemUI(); + } + } + + @Override + public void onBackPressed() { + if (mFragment.isPresent()) { + mFragment.get().onBackPressed(); + } + } + + private void hideSystemUI() { + getWindow().getDecorView().setSystemUiVisibility( + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_FULLSCREEN); + getWindow().setNavigationBarColor(Color.TRANSPARENT); + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java new file mode 100644 index 0000000000..486d6765d1 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialConfirmController.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 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.interaction; + +import android.view.View; + +import com.android.launcher3.R; +import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep; + +import java.util.Optional; + +/** + * An implementation of {@link BackGestureTutorialController} that defines the behavior of the + * {@link TutorialStep#CONFIRM}. + */ +final class BackGestureTutorialConfirmController extends BackGestureTutorialController { + + BackGestureTutorialConfirmController(BackGestureTutorialFragment fragment, + BackGestureTutorialTypeInfo tutorialTypeInfo) { + super(fragment, TutorialStep.CONFIRM, Optional.of(tutorialTypeInfo)); + } + + @Override + Optional getTitleStringId() { + return Optional.of(mTutorialTypeInfo.get().getTutorialConfirmTitleId()); + } + + @Override + Optional getSubtitleStringId() { + return Optional.of(mTutorialTypeInfo.get().getTutorialConfirmSubtitleId()); + } + + @Override + Optional getActionButtonStringId() { + return Optional.of(R.string.back_gesture_tutorial_action_button_label); + } + + @Override + Optional getActionTextButtonStringId() { + return Optional.of(R.string.back_gesture_tutorial_action_text_button_label); + } + + @Override + void onActionButtonClicked(View button) { + hideHandCoachingAnimation(); + if (button == mActionTextButton) { + mFragment.startSystemNavigationSetting(); + } + mFragment.closeTutorial(); + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java new file mode 100644 index 0000000000..3fe91a3a8b --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialController.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2020 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.interaction; + +import android.view.View; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.launcher3.R; +import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep; +import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType; + +import java.util.Optional; + +/** + * Defines the behavior of the particular {@link TutorialStep} and implements the transition to it. + */ +abstract class BackGestureTutorialController { + + final BackGestureTutorialFragment mFragment; + final TutorialStep mTutorialStep; + final Optional mTutorialTypeInfo; + final Button mActionTextButton; + final Button mActionButton; + final TextView mSubtitleTextView; + final ImageButton mCloseButton; + final BackGestureTutorialHandAnimation mHandCoachingAnimation; + final LinearLayout mTitlesContainer; + + private final TextView mTitleTextView; + private final ImageView mHandCoachingView; + + BackGestureTutorialController( + BackGestureTutorialFragment fragment, + TutorialStep tutorialStep, + Optional tutorialTypeInfo) { + mFragment = fragment; + mTutorialStep = tutorialStep; + mTutorialTypeInfo = tutorialTypeInfo; + + View rootView = fragment.getRootView(); + mActionTextButton = rootView.findViewById( + R.id.back_gesture_tutorial_fragment_action_text_button); + mActionButton = rootView.findViewById(R.id.back_gesture_tutorial_fragment_action_button); + mSubtitleTextView = rootView.findViewById( + R.id.back_gesture_tutorial_fragment_subtitle_view); + mTitleTextView = rootView.findViewById(R.id.back_gesture_tutorial_fragment_title_view); + mHandCoachingView = rootView.findViewById( + R.id.back_gesture_tutorial_fragment_hand_coaching); + mHandCoachingAnimation = mFragment.getHandAnimation(); + mHandCoachingView.bringToFront(); + mCloseButton = rootView.findViewById(R.id.back_gesture_tutorial_fragment_close_button); + mTitlesContainer = rootView.findViewById( + R.id.back_gesture_tutorial_fragment_titles_container); + } + + void transitToController() { + updateTitles(); + updateActionButtons(); + } + + void hideHandCoachingAnimation() { + mHandCoachingAnimation.stop(); + } + + void onGestureDetected() { + hideHandCoachingAnimation(); + + if (mTutorialStep == TutorialStep.CONFIRM) { + mFragment.closeTutorial(); + return; + } + + if (mTutorialTypeInfo.get().getTutorialType() == TutorialType.RIGHT_EDGE_BACK_NAVIGATION) { + mFragment.changeController(TutorialStep.ENGAGED, + TutorialType.LEFT_EDGE_BACK_NAVIGATION); + return; + } + + mFragment.changeController(TutorialStep.CONFIRM); + } + + abstract Optional getTitleStringId(); + + abstract Optional getSubtitleStringId(); + + abstract Optional getActionButtonStringId(); + + abstract Optional getActionTextButtonStringId(); + + abstract void onActionButtonClicked(View button); + + private void updateActionButtons() { + updateButton(mActionButton, getActionButtonStringId(), this::onActionButtonClicked); + updateButton(mActionTextButton, getActionTextButtonStringId(), this::onActionButtonClicked); + } + + private static void updateButton(Button button, Optional stringId, + View.OnClickListener listener) { + if (!stringId.isPresent()) { + button.setVisibility(View.INVISIBLE); + return; + } + + button.setVisibility(View.VISIBLE); + button.setText(stringId.get()); + button.setOnClickListener(listener); + } + + private void updateTitles() { + updateTitleView(mTitleTextView, getTitleStringId(), + R.style.TextAppearance_BackGestureTutorial_Title); + updateTitleView(mSubtitleTextView, getSubtitleStringId(), + R.style.TextAppearance_BackGestureTutorial_Subtitle); + } + + private static void updateTitleView(TextView textView, Optional stringId, + int styleId) { + if (!stringId.isPresent()) { + textView.setVisibility(View.GONE); + return; + } + + textView.setVisibility(View.VISIBLE); + textView.setText(stringId.get()); + textView.setTextAppearance(styleId); + } + + /** + * Constructs {@link BackGestureTutorialController} for providing {@link TutorialType} and + * {@link TutorialStep}. + */ + static Optional getTutorialController( + BackGestureTutorialFragment fragment, TutorialStep tutorialStep, + TutorialType tutorialType) { + BackGestureTutorialTypeInfo tutorialTypeInfo = + BackGestureTutorialTypeInfoProvider.getTutorialTypeInfo(tutorialType); + switch (tutorialStep) { + case ENGAGED: + return Optional.of( + new BackGestureTutorialEngagedController(fragment, tutorialTypeInfo)); + case CONFIRM: + return Optional.of( + new BackGestureTutorialConfirmController(fragment, tutorialTypeInfo)); + default: + throw new AssertionError("Unexpected tutorial step: " + tutorialStep); + } + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java new file mode 100644 index 0000000000..c9ee1e2006 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialEngagedController.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 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.interaction; + +import android.view.View; + +import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialStep; + +import java.util.Optional; + +/** + * An implementation of {@link BackGestureTutorialController} that defines the behavior of the + * {@link TutorialStep#ENGAGED}. + */ +final class BackGestureTutorialEngagedController extends BackGestureTutorialController { + + BackGestureTutorialEngagedController( + BackGestureTutorialFragment fragment, BackGestureTutorialTypeInfo tutorialTypeInfo) { + super(fragment, TutorialStep.ENGAGED, Optional.of(tutorialTypeInfo)); + } + + @Override + void transitToController() { + super.transitToController(); + mHandCoachingAnimation.maybeStartLoopedAnimation(mTutorialTypeInfo.get().getTutorialType()); + } + + @Override + Optional getTitleStringId() { + return Optional.of(mTutorialTypeInfo.get().getTutorialPlaygroundTitleId()); + } + + @Override + Optional getSubtitleStringId() { + return Optional.of(mTutorialTypeInfo.get().getTutorialEngagedSubtitleId()); + } + + @Override + Optional getActionButtonStringId() { + return Optional.empty(); + } + + @Override + Optional getActionTextButtonStringId() { + return Optional.empty(); + } + + @Override + void onActionButtonClicked(View button) { + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java new file mode 100644 index 0000000000..54408ceb6e --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialFragment.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2020 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.interaction; + +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.fragment.app.Fragment; + +import com.android.launcher3.R; + +import java.net.URISyntaxException; +import java.util.Optional; + +/** Shows the Back gesture interactive tutorial. */ +public class BackGestureTutorialFragment extends Fragment { + + private static final String LOG_TAG = "TutorialFragment"; + private static final String KEY_TUTORIAL_STEP = "tutorialStep"; + private static final String KEY_TUTORIAL_TYPE = "tutorialType"; + private static final String SYSTEM_NAVIGATION_SETTING_INTENT = + "#Intent;action=com.android.settings.SEARCH_RESULT_TRAMPOLINE;S" + + ".:settings:fragment_args_key=gesture_system_navigation_input_summary;S" + + ".:settings:show_fragment=com.android.settings.gestures" + + ".SystemNavigationGestureSettings;end"; + + private TutorialStep mTutorialStep; + private TutorialType mTutorialType; + private Optional mTutorialController = Optional.empty(); + private View mRootView; + private BackGestureTutorialHandAnimation mHandCoachingAnimation; + + public static BackGestureTutorialFragment newInstance( + TutorialStep tutorialStep, TutorialType tutorialType) { + BackGestureTutorialFragment fragment = new BackGestureTutorialFragment(); + Bundle args = new Bundle(); + args.putSerializable(KEY_TUTORIAL_STEP, tutorialStep); + args.putSerializable(KEY_TUTORIAL_TYPE, tutorialType); + fragment.setArguments(args); + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle args = savedInstanceState != null ? savedInstanceState : getArguments(); + mTutorialStep = (TutorialStep) args.getSerializable(KEY_TUTORIAL_STEP); + mTutorialType = (TutorialType) args.getSerializable(KEY_TUTORIAL_TYPE); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + + mRootView = inflater.inflate(R.layout.back_gesture_tutorial_fragment, + container, /* attachToRoot= */ false); + mRootView.findViewById(R.id.back_gesture_tutorial_fragment_close_button) + .setOnClickListener(this::onCloseButtonClicked); + mHandCoachingAnimation = new BackGestureTutorialHandAnimation(getContext(), mRootView); + + return mRootView; + } + + @Override + public void onResume() { + super.onResume(); + changeController(mTutorialStep, mTutorialType); + } + + @Override + public void onPause() { + super.onPause(); + mHandCoachingAnimation.stop(); + } + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + savedInstanceState.putSerializable(KEY_TUTORIAL_STEP, mTutorialStep); + savedInstanceState.putSerializable(KEY_TUTORIAL_TYPE, mTutorialType); + super.onSaveInstanceState(savedInstanceState); + } + + View getRootView() { + return mRootView; + } + + BackGestureTutorialHandAnimation getHandAnimation() { + return mHandCoachingAnimation; + } + + void changeController(TutorialStep tutorialStep) { + changeController(tutorialStep, mTutorialType); + } + + void changeController(TutorialStep tutorialStep, TutorialType tutorialType) { + Optional tutorialController = + BackGestureTutorialController.getTutorialController(/* fragment= */ this, + tutorialStep, tutorialType); + if (!tutorialController.isPresent()) { + return; + } + + mTutorialController = tutorialController; + mTutorialController.get().transitToController(); + this.mTutorialStep = mTutorialController.get().mTutorialStep; + this.mTutorialType = tutorialType; + } + + void onBackPressed() { + if (mTutorialController.isPresent()) { + mTutorialController.get().onGestureDetected(); + } + } + + void closeTutorial() { + getActivity().finish(); + } + + void startSystemNavigationSetting() { + try { + startActivityForResult( + Intent.parseUri(SYSTEM_NAVIGATION_SETTING_INTENT, /* flags= */ 0), + /* requestCode= */ 0); + } catch (URISyntaxException e) { + Log.e(LOG_TAG, "The launch Intent Uri is wrong syntax: " + e); + } catch (ActivityNotFoundException e) { + Log.e(LOG_TAG, "The launch Activity not found: " + e); + } + } + + private void onCloseButtonClicked(View button) { + closeTutorial(); + } + + /** Denotes the step of the tutorial. */ + enum TutorialStep { + ENGAGED, + CONFIRM, + } + + /** Denotes the type of the tutorial. */ + enum TutorialType { + RIGHT_EDGE_BACK_NAVIGATION, + LEFT_EDGE_BACK_NAVIGATION, + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java new file mode 100644 index 0000000000..d03811de55 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialHandAnimation.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2020 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.interaction; + +import android.content.Context; +import android.graphics.drawable.Animatable2; +import android.graphics.drawable.AnimatedVectorDrawable; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.ImageView; + +import androidx.core.content.ContextCompat; + +import com.android.launcher3.R; +import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType; + +import java.time.Duration; + +/** Hand coaching animation. */ +final class BackGestureTutorialHandAnimation { + + // A delay for waiting the Activity fully launches. + private static final Duration ANIMATION_START_DELAY = Duration.ofMillis(300L); + + private final ImageView mHandCoachingView; + private final AnimatedVectorDrawable mGestureAnimation; + + private boolean mIsAnimationPlayed = false; + + BackGestureTutorialHandAnimation(Context context, View rootView) { + mHandCoachingView = rootView.findViewById( + R.id.back_gesture_tutorial_fragment_hand_coaching); + mGestureAnimation = (AnimatedVectorDrawable) ContextCompat.getDrawable(context, + R.drawable.back_gesture); + } + + boolean isRunning() { + return mGestureAnimation.isRunning(); + } + + /** + * Starts animation if the playground is launched for the first time. + */ + void maybeStartLoopedAnimation(TutorialType tutorialType) { + if (isRunning() || mIsAnimationPlayed) { + return; + } + + mIsAnimationPlayed = true; + clearAnimationCallbacks(); + mGestureAnimation.registerAnimationCallback( + new Animatable2.AnimationCallback() { + @Override + public void onAnimationEnd(Drawable drawable) { + super.onAnimationEnd(drawable); + mGestureAnimation.start(); + } + }); + start(tutorialType); + } + + private void start(TutorialType tutorialType) { + // Because the gesture animation has only the right side form. + // The left side form of the gesture animation is made from flipping the View. + float rotationY = tutorialType == TutorialType.LEFT_EDGE_BACK_NAVIGATION ? 180f : 0f; + mHandCoachingView.setRotationY(rotationY); + mHandCoachingView.setImageDrawable(mGestureAnimation); + mHandCoachingView.postDelayed(() -> mGestureAnimation.start(), + ANIMATION_START_DELAY.toMillis()); + } + + private void clearAnimationCallbacks() { + mGestureAnimation.clearAnimationCallbacks(); + } + + void stop() { + mIsAnimationPlayed = false; + clearAnimationCallbacks(); + mGestureAnimation.stop(); + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java new file mode 100644 index 0000000000..ac8443d588 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfo.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 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.interaction; + +import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType; + +/** Defines the UI element identifiers for the particular {@link TutorialType}. */ +final class BackGestureTutorialTypeInfo { + + private final TutorialType mTutorialType; + private final int mTutorialPlaygroundTitleId; + private final int mTutorialEngagedSubtitleId; + private final int mTutorialConfirmTitleId; + private final int mTutorialConfirmSubtitleId; + + TutorialType getTutorialType() { + return mTutorialType; + } + + int getTutorialPlaygroundTitleId() { + return mTutorialPlaygroundTitleId; + } + + int getTutorialEngagedSubtitleId() { + return mTutorialEngagedSubtitleId; + } + + int getTutorialConfirmTitleId() { + return mTutorialConfirmTitleId; + } + + int getTutorialConfirmSubtitleId() { + return mTutorialConfirmSubtitleId; + } + + static Builder builder() { + return new Builder(); + } + + private BackGestureTutorialTypeInfo( + TutorialType tutorialType, + int tutorialPlaygroundTitleId, + int tutorialEngagedSubtitleId, + int tutorialConfirmTitleId, + int tutorialConfirmSubtitleId) { + mTutorialType = tutorialType; + mTutorialPlaygroundTitleId = tutorialPlaygroundTitleId; + mTutorialEngagedSubtitleId = tutorialEngagedSubtitleId; + mTutorialConfirmTitleId = tutorialConfirmTitleId; + mTutorialConfirmSubtitleId = tutorialConfirmSubtitleId; + } + + /** Builder for producing {@link BackGestureTutorialTypeInfo} objects. */ + static class Builder { + + private TutorialType mTutorialType; + private Integer mTutorialPlaygroundTitleId; + private Integer mTutorialEngagedSubtitleId; + private Integer mTutorialConfirmTitleId; + private Integer mTutorialConfirmSubtitleId; + + Builder setTutorialType(TutorialType tutorialType) { + mTutorialType = tutorialType; + return this; + } + + Builder setTutorialPlaygroundTitleId(int stringId) { + mTutorialPlaygroundTitleId = stringId; + return this; + } + + Builder setTutorialEngagedSubtitleId(int stringId) { + mTutorialEngagedSubtitleId = stringId; + return this; + } + + Builder setTutorialConfirmTitleId(int stringId) { + mTutorialConfirmTitleId = stringId; + return this; + } + + Builder setTutorialConfirmSubtitleId(int stringId) { + mTutorialConfirmSubtitleId = stringId; + return this; + } + + BackGestureTutorialTypeInfo build() { + return new BackGestureTutorialTypeInfo( + mTutorialType, + mTutorialPlaygroundTitleId, + mTutorialEngagedSubtitleId, + mTutorialConfirmTitleId, + mTutorialConfirmSubtitleId); + } + } +} diff --git a/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java new file mode 100644 index 0000000000..9575d833a2 --- /dev/null +++ b/quickstep/src/com/android/quickstep/interaction/BackGestureTutorialTypeInfoProvider.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2020 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.interaction; + +import com.android.launcher3.R; +import com.android.quickstep.interaction.BackGestureTutorialFragment.TutorialType; + +/** Provides instances of {@link BackGestureTutorialTypeInfo} for each {@link TutorialType}. */ +final class BackGestureTutorialTypeInfoProvider { + + private static final BackGestureTutorialTypeInfo RIGHT_EDGE_BACK_NAV_TUTORIAL_INFO = + BackGestureTutorialTypeInfo.builder() + .setTutorialType(TutorialType.RIGHT_EDGE_BACK_NAVIGATION) + .setTutorialPlaygroundTitleId( + R.string.back_gesture_tutorial_playground_title_swipe_inward_right_edge) + .setTutorialEngagedSubtitleId( + R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_right_edge) + .setTutorialConfirmTitleId(R.string.back_gesture_tutorial_confirm_title) + .setTutorialConfirmSubtitleId(R.string.back_gesture_tutorial_confirm_subtitle) + .build(); + + private static final BackGestureTutorialTypeInfo LEFT_EDGE_BACK_NAV_TUTORIAL_INFO = + BackGestureTutorialTypeInfo.builder() + .setTutorialType(TutorialType.LEFT_EDGE_BACK_NAVIGATION) + .setTutorialPlaygroundTitleId( + R.string.back_gesture_tutorial_playground_title_swipe_inward_left_edge) + .setTutorialEngagedSubtitleId( + R.string.back_gesture_tutorial_engaged_subtitle_swipe_inward_left_edge) + .setTutorialConfirmTitleId(R.string.back_gesture_tutorial_confirm_title) + .setTutorialConfirmSubtitleId(R.string.back_gesture_tutorial_confirm_subtitle) + .build(); + + static BackGestureTutorialTypeInfo getTutorialTypeInfo(TutorialType tutorialType) { + switch (tutorialType) { + case RIGHT_EDGE_BACK_NAVIGATION: + return RIGHT_EDGE_BACK_NAV_TUTORIAL_INFO; + case LEFT_EDGE_BACK_NAVIGATION: + return LEFT_EDGE_BACK_NAV_TUTORIAL_INFO; + default: + throw new AssertionError("Unexpected tutorial type: " + tutorialType); + } + } + + private BackGestureTutorialTypeInfoProvider() { + } +}