Merge "Implement initial transient Taskbar EDU tooltips." into tm-qpr-dev am: 654f714718

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/20795522

Change-Id: Ie43422c362b180ca2d1124b1d7bf514276da101d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
TreeHugger Robot
2023-01-21 08:10:03 +00:00
committed by Automerger Merge Worker
20 changed files with 686 additions and 85 deletions

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle">
<corners android:radius="@dimen/dialogCornerRadius" />
<solid android:color="?androidprv:attr/colorSurface" />
</shape>

View File

@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/title"
style="@style/TextAppearance.TaskbarEduTooltip.Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/taskbar_edu_features"
app:layout_constraintEnd_toEndOf="@id/suggestions_animation"
app:layout_constraintStart_toStartOf="@id/splitscreen_animation"
app:layout_constraintTop_toTopOf="parent" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/splitscreen_animation"
android:layout_width="@dimen/taskbar_edu_features_lottie_width"
android:layout_height="@dimen/taskbar_edu_features_lottie_height"
android:layout_marginTop="@dimen/taskbar_edu_tooltip_vertical_margin"
android:scaleType="centerCrop"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="@raw/taskbar_edu_splitscreen_transient" />
<TextView
android:id="@+id/splitscreen_text"
style="@style/TextAppearance.TaskbarEduTooltip.Subtext"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/taskbar_edu_splitscreen"
app:layout_constraintEnd_toEndOf="@id/splitscreen_animation"
app:layout_constraintStart_toStartOf="@id/splitscreen_animation"
app:layout_constraintTop_toBottomOf="@id/splitscreen_animation" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/suggestions_animation"
android:layout_width="@dimen/taskbar_edu_features_lottie_width"
android:layout_height="@dimen/taskbar_edu_features_lottie_height"
android:layout_marginStart="@dimen/taskbar_edu_features_horizontal_spacing"
android:layout_marginTop="@dimen/taskbar_edu_tooltip_vertical_margin"
android:scaleType="centerCrop"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/splitscreen_animation"
app:layout_constraintTop_toBottomOf="@id/title"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="@raw/taskbar_edu_suggestions_transient" />
<TextView
android:id="@+id/suggestions_text"
style="@style/TextAppearance.TaskbarEduTooltip.Subtext"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/taskbar_edu_suggestions"
app:layout_constraintEnd_toEndOf="@id/suggestions_animation"
app:layout_constraintStart_toStartOf="@id/suggestions_animation"
app:layout_constraintTop_toBottomOf="@id/suggestions_animation" />
<Button
android:id="@+id/done_button"
style="@style/TaskbarEdu.Button.Next"
android:layout_width="wrap_content"
android:layout_height="36dp"
android:layout_marginTop="32dp"
android:text="@string/taskbar_edu_done"
android:textColor="?androidprv:attr/textColorOnAccent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/suggestions_text" />
</merge>

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/title"
style="@style/TextAppearance.TaskbarEduTooltip.Title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/taskbar_edu_stashing"
app:layout_constraintEnd_toEndOf="@id/animation"
app:layout_constraintStart_toStartOf="@id/animation"
app:layout_constraintTop_toTopOf="parent" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/animation"
android:layout_width="@dimen/taskbar_edu_swipe_lottie_width"
android:layout_height="@dimen/taskbar_edu_swipe_lottie_height"
android:layout_marginTop="@dimen/taskbar_edu_tooltip_vertical_margin"
android:scaleType="centerCrop"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="@raw/taskbar_edu_stashing_transient" />
</merge>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2023 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.
-->
<com.android.launcher3.taskbar.TaskbarEduTooltip xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:layout_marginBottom="16dp"
android:clipChildren="false"
android:clipToPadding="false"
android:fitsSystemWindows="true"
android:gravity="center"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_taskbar_edu_tooltip"
android:elevation="@dimen/taskbar_edu_tooltip_elevation"
android:paddingHorizontal="@dimen/taskbar_edu_tooltip_horizontal_margin"
android:paddingVertical="@dimen/taskbar_edu_tooltip_vertical_margin" />
<View
android:id="@+id/arrow"
android:layout_width="@dimen/popup_arrow_width"
android:layout_height="@dimen/popup_arrow_height"
android:elevation="@dimen/taskbar_edu_tooltip_elevation" />
</com.android.launcher3.taskbar.TaskbarEduTooltip>

View File

@@ -309,6 +309,18 @@
<dimen name="taskbar_button_margin_6_5">75dp</dimen>
<dimen name="taskbar_button_margin_default">48dp</dimen>
<!-- Taskbar education tooltip -->
<dimen name="taskbar_edu_tooltip_elevation">14dp</dimen>
<dimen name="taskbar_edu_tooltip_horizontal_margin">32dp</dimen>
<dimen name="taskbar_edu_tooltip_vertical_margin">24dp</dimen>
<dimen name="taskbar_edu_tooltip_enter_y_delta">20dp</dimen>
<dimen name="taskbar_edu_tooltip_exit_y_delta">-10dp</dimen>
<dimen name="taskbar_edu_swipe_lottie_width">348dp</dimen>
<dimen name="taskbar_edu_swipe_lottie_height">217dp</dimen>
<dimen name="taskbar_edu_features_lottie_width">170dp</dimen>
<dimen name="taskbar_edu_features_lottie_height">106dp</dimen>
<dimen name="taskbar_edu_features_horizontal_spacing">24dp</dimen>
<!-- Recents overview -->
<dimen name="recents_filter_icon_size">30dp</dimen>

View File

@@ -238,11 +238,13 @@
<!-- Accessibility text spoken when the taskbar education panel disappears [CHAR_LIMIT=NONE] -->
<string name="taskbar_edu_closed">Taskbar education closed</string>
<!-- Text in dialog that lets a user know how they can use the taskbar to use multiple apps at once on their device. [CHAR_LIMIT=60] -->
<string name="taskbar_edu_splitscreen">Drag to the side to use 2 apps at once</string>
<string name="taskbar_edu_splitscreen">Drag an app to the side to use 2 apps at once</string>
<!-- Text in dialog that lets a user know how they can show the taskbar on their device. [CHAR_LIMIT=60] -->
<string name="taskbar_edu_stashing">Short swipe up to show the taskbar</string>
<!-- Text in dialog that lets a user know how the taskbar suggests apps based on their usage. [CHAR_LIMIT=60] -->
<string name="taskbar_edu_suggestions">The taskbar suggests apps based on your routine</string>
<string name="taskbar_edu_suggestions">Get app suggestions based on your routine</string>
<!-- Title in dialog that shows a user what they can do with the taskbar. [CHAR_LIMIT=60] -->
<string name="taskbar_edu_features">Do more with the taskbar</string>
<!-- Text on button to go to the next screen of a tutorial [CHAR_LIMIT=16] -->
<string name="taskbar_edu_next">Next</string>
<!-- Text on button to go to the previous screen of a tutorial [CHAR_LIMIT=16] -->

View File

@@ -186,4 +186,16 @@
<item name="android:textSize">24sp</item>
<item name="android:lines">2</item>
</style>
<style name="TextAppearance.TaskbarEduTooltip.Title" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle">
<item name="android:gravity">center_horizontal</item>
<item name="android:fontFamily">google-sans</item>
<item name="android:textSize">24sp</item>
</style>
<style name="TextAppearance.TaskbarEduTooltip.Subtext" parent="android:TextAppearance.Material.Body1">
<item name="android:layout_marginTop">16dp</item>
<item name="android:fontFamily">google-sans-text</item>
<item name="android:textSize">14sp</item>
</style>
</resources>

View File

@@ -699,7 +699,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
@Override
public void onAnimationStart(Animator animation) {
LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
if (taskbarController != null && taskbarController.shouldShowEdu()) {
if (taskbarController != null && taskbarController.shouldShowEduOnAppLaunch()) {
// LAUNCHER_TASKBAR_EDUCATION_SHOWING is set to true here, when the education
// flow is about to start, to avoid a race condition with other components
// that would show something else to the user as soon as the app is opened.
@@ -715,7 +715,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController();
if (taskbarController != null) {
taskbarController.showEdu();
taskbarController.showEduOnAppLaunch();
}
openingTargets.release();
}

View File

@@ -20,6 +20,8 @@ import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
@@ -32,10 +34,12 @@ public abstract class BaseTaskbarContext extends ContextThemeWrapper implements
protected final LayoutInflater mLayoutInflater;
private final List<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>();
private final OnboardingPrefs<BaseTaskbarContext> mOnboardingPrefs;
public BaseTaskbarContext(Context windowContext) {
super(windowContext, Themes.getActivityThemeRes(windowContext));
mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
mOnboardingPrefs = new OnboardingPrefs<>(this, LauncherPrefs.getPrefs(this));
}
@Override
@@ -48,6 +52,11 @@ public abstract class BaseTaskbarContext extends ContextThemeWrapper implements
return mDPChangeListeners;
}
@Override
public OnboardingPrefs<BaseTaskbarContext> getOnboardingPrefs() {
return mOnboardingPrefs;
}
/** Callback invoked when a drag is initiated within this context. */
public abstract void onDragStart();

View File

@@ -18,6 +18,8 @@ package com.android.launcher3.taskbar;
import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_EDU_TOOLTIP;
import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_RESUMED;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
@@ -259,23 +261,51 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
}
/**
* Starts the taskbar education flow, if the user hasn't seen it yet.
* Starts a Taskbar EDU flow, if the user should see one upon launching an application.
*/
public void showEdu() {
if (!shouldShowEdu()) {
public void showEduOnAppLaunch() {
if (!shouldShowEduOnAppLaunch()) {
return;
}
mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.TASKBAR_EDU_SEEN);
mControllers.taskbarEduController.showEdu();
// Transient and persistent bottom sheet.
if (!ENABLE_TASKBAR_EDU_TOOLTIP.get()) {
mLauncher.getOnboardingPrefs().markChecked(OnboardingPrefs.TASKBAR_EDU_SEEN);
mControllers.taskbarEduController.showEdu();
return;
}
// Persistent features EDU tooltip.
if (!DisplayController.isTransientTaskbar(mLauncher)) {
mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
return;
}
// Transient swipe EDU tooltip.
mControllers.taskbarEduTooltipController.maybeShowSwipeEdu();
}
/**
* Whether the taskbar education should be shown.
* Returns {@code true} if a Taskbar education should be shown on application launch.
*/
public boolean shouldShowEdu() {
return !Utilities.IS_RUNNING_IN_TEST_HARNESS
&& !mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN);
public boolean shouldShowEduOnAppLaunch() {
if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
return false;
}
// Transient and persistent bottom sheet.
if (!ENABLE_TASKBAR_EDU_TOOLTIP.get()) {
return !mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN);
}
// Persistent features EDU tooltip.
if (!DisplayController.isTransientTaskbar(mLauncher)) {
return !mLauncher.getOnboardingPrefs().hasReachedMaxCount(
OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP);
}
// Transient swipe EDU tooltip.
return mControllers.taskbarEduTooltipController.getTooltipStep() < TOOLTIP_STEP_FEATURES;
}
@Override

View File

@@ -233,7 +233,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
new TaskbarTranslationController(this),
isDesktopMode
? new DesktopTaskbarRecentAppsController(this)
: TaskbarRecentAppsController.DEFAULT);
: TaskbarRecentAppsController.DEFAULT,
new TaskbarEduTooltipController(this));
}
public void init(@NonNull TaskbarSharedState sharedState) {
@@ -915,6 +916,7 @@ public class TaskbarActivityContext extends BaseTaskbarContext {
*/
public void onSwipeToUnstashTaskbar() {
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(false);
mControllers.taskbarEduTooltipController.hide();
}
/** Returns {@code true} if taskbar All Apps is open. */

View File

@@ -39,11 +39,14 @@ public class TaskbarAutohideSuspendController implements
public static final int FLAG_AUTOHIDE_SUSPEND_DRAGGING = 1 << 1;
// User has touched down but has not lifted finger.
public static final int FLAG_AUTOHIDE_SUSPEND_TOUCHING = 1 << 2;
// Taskbar EDU overlay is open above the Taskbar. */
public static final int FLAG_AUTOHIDE_SUSPEND_EDU_OPEN = 1 << 3;
@IntDef(flag = true, value = {
FLAG_AUTOHIDE_SUSPEND_FULLSCREEN,
FLAG_AUTOHIDE_SUSPEND_DRAGGING,
FLAG_AUTOHIDE_SUSPEND_TOUCHING,
FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
})
@Retention(RetentionPolicy.SOURCE)
public @interface AutohideSuspendFlag {}
@@ -102,6 +105,7 @@ public class TaskbarAutohideSuspendController implements
"FLAG_AUTOHIDE_SUSPEND_FULLSCREEN");
appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_DRAGGING, "FLAG_AUTOHIDE_SUSPEND_DRAGGING");
appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_TOUCHING, "FLAG_AUTOHIDE_SUSPEND_TOUCHING");
appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_EDU_OPEN, "FLAG_AUTOHIDE_SUSPEND_EDU_OPEN");
return str.toString();
}
}

View File

@@ -58,6 +58,7 @@ public class TaskbarControllers {
public final TaskbarRecentAppsController taskbarRecentAppsController;
public final TaskbarTranslationController taskbarTranslationController;
public final TaskbarOverlayController taskbarOverlayController;
public final TaskbarEduTooltipController taskbarEduTooltipController;
@Nullable private LoggableTaskbarController[] mControllersToLog = null;
@Nullable private BackgroundRendererController[] mBackgroundRendererControllers = null;
@@ -101,7 +102,8 @@ public class TaskbarControllers {
TaskbarInsetsController taskbarInsetsController,
VoiceInteractionWindowController voiceInteractionWindowController,
TaskbarTranslationController taskbarTranslationController,
TaskbarRecentAppsController taskbarRecentAppsController) {
TaskbarRecentAppsController taskbarRecentAppsController,
TaskbarEduTooltipController taskbarEduTooltipController) {
this.taskbarActivityContext = taskbarActivityContext;
this.taskbarDragController = taskbarDragController;
this.navButtonController = navButtonController;
@@ -124,6 +126,7 @@ public class TaskbarControllers {
this.voiceInteractionWindowController = voiceInteractionWindowController;
this.taskbarTranslationController = taskbarTranslationController;
this.taskbarRecentAppsController = taskbarRecentAppsController;
this.taskbarEduTooltipController = taskbarEduTooltipController;
}
/**
@@ -155,6 +158,7 @@ public class TaskbarControllers {
voiceInteractionWindowController.init(this);
taskbarRecentAppsController.init(this);
taskbarTranslationController.init(this);
taskbarEduTooltipController.init(this);
mControllersToLog = new LoggableTaskbarController[] {
taskbarDragController, navButtonController, navbarButtonsViewController,
@@ -162,7 +166,8 @@ public class TaskbarControllers {
taskbarUnfoldAnimationController, taskbarKeyguardController,
stashedHandleViewController, taskbarStashController, taskbarEduController,
taskbarAutohideSuspendController, taskbarPopupController, taskbarInsetsController,
voiceInteractionWindowController, taskbarTranslationController
voiceInteractionWindowController, taskbarTranslationController,
taskbarEduTooltipController
};
mBackgroundRendererControllers = new BackgroundRendererController[] {
taskbarDragLayerController, taskbarScrimViewController,

View File

@@ -0,0 +1,187 @@
/*
* Copyright (C) 2023 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.launcher3.taskbar
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.content.Context
import android.provider.Settings
import android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_DOWN
import android.view.View
import android.view.ViewGroup
import android.view.animation.Interpolator
import com.android.launcher3.AbstractFloatingView
import com.android.launcher3.R
import com.android.launcher3.anim.AnimatorListeners
import com.android.launcher3.popup.RoundedArrowDrawable
import com.android.launcher3.util.Themes
import com.android.launcher3.views.ActivityContext
import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.systemui.animation.Interpolators.STANDARD
private const val ENTER_DURATION_MS = 300L
private const val EXIT_DURATION_MS = 150L
/** Floating tooltip for Taskbar education. */
class TaskbarEduTooltip
@JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : AbstractFloatingView(context, attrs, defStyleAttr) {
private val activityContext: ActivityContext = ActivityContext.lookupContext(context)
private val backgroundColor =
Themes.getAttrColor(context, com.android.internal.R.attr.colorSurface)
private val tooltipCornerRadius = Themes.getDialogCornerRadius(context)
private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height)
private val arrowPointRadius = resources.getDimension(R.dimen.popup_arrow_corner_radius)
private val enterYDelta = resources.getDimension(R.dimen.taskbar_edu_tooltip_enter_y_delta)
private val exitYDelta = resources.getDimension(R.dimen.taskbar_edu_tooltip_exit_y_delta)
/** Container where the tooltip's body should be inflated. */
lateinit var content: ViewGroup
private set
private lateinit var arrow: View
/** Callback invoked when the tooltip is being closed. */
var onCloseCallback: () -> Unit = {}
private var openCloseAnimator: AnimatorSet? = null
/** Animates the tooltip into view. */
fun show() {
if (isOpen) {
return
}
mIsOpen = true
activityContext.dragLayer.addView(this)
openCloseAnimator = createOpenCloseAnimator(isOpening = true).apply { start() }
}
override fun onFinishInflate() {
super.onFinishInflate()
content = findViewById(R.id.content)
arrow = findViewById(R.id.arrow)
arrow.background =
RoundedArrowDrawable(
arrowWidth,
arrowHeight,
arrowPointRadius,
tooltipCornerRadius,
measuredWidth.toFloat(),
measuredHeight.toFloat(),
(measuredWidth - arrowWidth) / 2, // arrowOffsetX
0f, // arrowOffsetY
false, // isPointingUp
true, // leftAligned
backgroundColor,
)
}
override fun handleClose(animate: Boolean) {
if (!isOpen) {
return
}
onCloseCallback()
if (!animate) {
return closeComplete()
}
openCloseAnimator?.cancel()
openCloseAnimator = createOpenCloseAnimator(isOpening = false)
openCloseAnimator?.addListener(AnimatorListeners.forEndCallback(this::closeComplete))
openCloseAnimator?.start()
}
override fun isOfType(type: Int): Boolean = type and TYPE_TASKBAR_EDUCATION_DIALOG != 0
override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
if (ev?.action == ACTION_DOWN && !activityContext.dragLayer.isEventOverView(this, ev)) {
close(true)
}
return false
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
Settings.Secure.putInt(mContext.contentResolver, LAUNCHER_TASKBAR_EDUCATION_SHOWING, 0)
}
private fun closeComplete() {
openCloseAnimator?.cancel()
openCloseAnimator = null
mIsOpen = false
activityContext.dragLayer.removeView(this)
}
private fun createOpenCloseAnimator(isOpening: Boolean): AnimatorSet {
val duration: Long
val alphaValues: FloatArray
val translateYValues: FloatArray
val fadeInterpolator: Interpolator
val translateYInterpolator: Interpolator
if (isOpening) {
duration = ENTER_DURATION_MS
alphaValues = floatArrayOf(0f, 1f)
translateYValues = floatArrayOf(enterYDelta, 0f)
fadeInterpolator = STANDARD
translateYInterpolator = EMPHASIZED_DECELERATE
} else {
duration = EXIT_DURATION_MS
alphaValues = floatArrayOf(1f, 0f)
translateYValues = floatArrayOf(0f, exitYDelta)
fadeInterpolator = EMPHASIZED_ACCELERATE
translateYInterpolator = EMPHASIZED_ACCELERATE
}
val fade =
ValueAnimator.ofFloat(*alphaValues).apply {
interpolator = fadeInterpolator
addUpdateListener {
val alpha = it.animatedValue as Float
content.alpha = alpha
arrow.alpha = alpha
}
}
val translateY =
ValueAnimator.ofFloat(*translateYValues).apply {
interpolator = translateYInterpolator
addUpdateListener {
val translationY = it.animatedValue as Float
content.translationY = translationY
arrow.translationY = translationY
}
}
return AnimatorSet().apply {
this.duration = duration
playTogether(fade, translateY)
}
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright (C) 2023 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.launcher3.taskbar
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IntDef
import androidx.annotation.LayoutRes
import com.android.launcher3.R
import com.android.launcher3.Utilities.IS_RUNNING_IN_TEST_HARNESS
import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_EDU_TOOLTIP
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
import com.android.launcher3.util.DisplayController
import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
import java.io.PrintWriter
/** First EDU step for swiping up to show transient Taskbar. */
const val TOOLTIP_STEP_SWIPE = 0
/** Second EDU step for explaining Taskbar functionality when unstashed. */
const val TOOLTIP_STEP_FEATURES = 1
/**
* EDU is completed.
*
* This value should match the maximum count for [TASKBAR_EDU_TOOLTIP_STEP].
*/
const val TOOLTIP_STEP_NONE = 2
/** Current step in the tooltip EDU flow. */
@Retention(AnnotationRetention.SOURCE)
@IntDef(TOOLTIP_STEP_SWIPE, TOOLTIP_STEP_FEATURES, TOOLTIP_STEP_NONE)
annotation class TaskbarEduTooltipStep
/** Controls stepping through the Taskbar tooltip EDU. */
class TaskbarEduTooltipController(val activityContext: TaskbarActivityContext) :
LoggableTaskbarController {
private val isTooltipEnabled = !IS_RUNNING_IN_TEST_HARNESS && ENABLE_TASKBAR_EDU_TOOLTIP.get()
private val isOpen: Boolean
get() = tooltip?.isOpen ?: false
private lateinit var controllers: TaskbarControllers
@TaskbarEduTooltipStep
var tooltipStep: Int
get() {
return activityContext.onboardingPrefs?.getCount(TASKBAR_EDU_TOOLTIP_STEP)
?: TOOLTIP_STEP_NONE
}
private set(step) {
activityContext.onboardingPrefs?.setEventCount(step, TASKBAR_EDU_TOOLTIP_STEP)
}
private var tooltip: TaskbarEduTooltip? = null
fun init(controllers: TaskbarControllers) {
this.controllers = controllers
}
/** Shows swipe EDU tooltip if it is the current [tooltipStep]. */
fun maybeShowSwipeEdu() {
if (
!isTooltipEnabled ||
!DisplayController.isTransientTaskbar(activityContext) ||
tooltipStep > TOOLTIP_STEP_SWIPE
) {
return
}
tooltipStep = TOOLTIP_STEP_FEATURES
inflateTooltip(R.layout.taskbar_edu_swipe)
tooltip?.show()
}
/**
* Shows feature EDU tooltip if this step has not been seen.
*
* If [TOOLTIP_STEP_SWIPE] has not been seen at this point, the first step is skipped because a
* swipe up is necessary to show this step.
*/
fun maybeShowFeaturesEdu() {
if (!isTooltipEnabled || tooltipStep > TOOLTIP_STEP_FEATURES) {
return
}
tooltipStep = TOOLTIP_STEP_NONE
inflateTooltip(R.layout.taskbar_edu_features)
tooltip?.apply {
findViewById<View>(R.id.done_button)?.setOnClickListener { hide() }
if (DisplayController.isTransientTaskbar(activityContext)) {
(layoutParams as ViewGroup.MarginLayoutParams).bottomMargin +=
activityContext.deviceProfile.taskbarSize
}
show()
}
}
/** Closes the current [tooltip]. */
fun hide() = tooltip?.close(true)
/** Initializes [tooltip] with content from [contentResId]. */
private fun inflateTooltip(@LayoutRes contentResId: Int) {
val overlayContext = controllers.taskbarOverlayController.requestWindow()
val tooltip =
overlayContext.layoutInflater.inflate(
R.layout.taskbar_edu_tooltip,
overlayContext.dragLayer,
false
) as TaskbarEduTooltip
controllers.taskbarAutohideSuspendController.updateFlag(
FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
true
)
tooltip.onCloseCallback = {
this.tooltip = null
controllers.taskbarAutohideSuspendController.updateFlag(
FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
false
)
controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
}
overlayContext.layoutInflater.inflate(contentResId, tooltip.content, true)
this.tooltip = tooltip
}
override fun dumpLogs(prefix: String?, pw: PrintWriter?) {
pw?.println("$(prefix)TaskbarEduController:")
pw?.println("$prefix\tisTooltipEnabled=$isTooltipEnabled")
pw?.println("$prefix\tisOpen=$isOpen")
pw?.println("$prefix\ttooltipStep=$tooltipStep")
}
}

View File

@@ -426,6 +426,11 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
return;
}
if (stash && mControllers.taskbarAutohideSuspendController.isSuspended()) {
// Avoid stashing if autohide is currently suspended.
return;
}
if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO) != stash) {
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash);
applyState();

View File

@@ -109,8 +109,12 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
.setStiffness(SpringForce.STIFFNESS_LOW)
.build(mTranslationYForSwipe, VALUE);
mSpringBounce.addListener(forEndCallback(() -> {
if (mGestureEnded) {
reset();
if (!mGestureEnded) {
return;
}
reset();
if (mControllers.taskbarStashController.isInAppAndNotStashed()) {
mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
}
}));
mSpringBounce.start();

View File

@@ -19,7 +19,6 @@ import android.content.Context;
import android.view.View;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -30,7 +29,6 @@ import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.TaskbarDragController;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView;
import com.android.launcher3.util.OnboardingPrefs;
/**
* Window context for the taskbar overlays such as All Apps and EDU.
@@ -40,7 +38,6 @@ import com.android.launcher3.util.OnboardingPrefs;
*/
public class TaskbarOverlayContext extends BaseTaskbarContext {
private final TaskbarActivityContext mTaskbarContext;
private final OnboardingPrefs<TaskbarOverlayContext> mOnboardingPrefs;
private final TaskbarOverlayController mOverlayController;
private final TaskbarDragController mDragController;
@@ -59,7 +56,6 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
mOverlayController = controllers.taskbarOverlayController;
mDragController = new TaskbarDragController(this);
mDragController.init(controllers);
mOnboardingPrefs = new OnboardingPrefs<>(this, LauncherPrefs.getPrefs(this));
mDragLayer = new TaskbarOverlayDragLayer(this);
TaskbarStashController taskbarStashController = controllers.taskbarStashController;
@@ -99,11 +95,6 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
return mDragLayer.findViewById(R.id.apps_view);
}
@Override
public OnboardingPrefs<TaskbarOverlayContext> getOnboardingPrefs() {
return mOnboardingPrefs;
}
@Override
public boolean isBindingItems() {
return mTaskbarContext.isBindingItems();

View File

@@ -23,55 +23,35 @@ import org.mockito.Mock
import org.mockito.MockitoAnnotations
/**
* Helper class to extend to get access to all controllers.
* Gotta be careful of your relationship with this class though, it can be quite... controlling.
* Helper class to extend to get access to all controllers. Gotta be careful of your relationship
* with this class though, it can be quite... controlling.
*/
abstract class TaskbarBaseTestCase {
@Mock
lateinit var taskbarActivityContext: TaskbarActivityContext
@Mock
lateinit var taskbarDragController: TaskbarDragController
@Mock
lateinit var navButtonController: TaskbarNavButtonController
@Mock
lateinit var navbarButtonsViewController: NavbarButtonsViewController
@Mock
lateinit var rotationButtonController: RotationButtonController
@Mock
lateinit var taskbarDragLayerController: TaskbarDragLayerController
@Mock
lateinit var taskbarScrimViewController: TaskbarScrimViewController
@Mock
lateinit var taskbarViewController: TaskbarViewController
@Mock
lateinit var taskbarUnfoldAnimationController: TaskbarUnfoldAnimationController
@Mock
lateinit var taskbarKeyguardController: TaskbarKeyguardController
@Mock
lateinit var stashedHandleViewController: StashedHandleViewController
@Mock
lateinit var taskbarStashController: TaskbarStashController
@Mock
lateinit var taskbarEduController: TaskbarEduController
@Mock
lateinit var taskbarAutohideSuspendController: TaskbarAutohideSuspendController
@Mock
lateinit var taskbarPopupController: TaskbarPopupController
@Mock lateinit var taskbarActivityContext: TaskbarActivityContext
@Mock lateinit var taskbarDragController: TaskbarDragController
@Mock lateinit var navButtonController: TaskbarNavButtonController
@Mock lateinit var navbarButtonsViewController: NavbarButtonsViewController
@Mock lateinit var rotationButtonController: RotationButtonController
@Mock lateinit var taskbarDragLayerController: TaskbarDragLayerController
@Mock lateinit var taskbarScrimViewController: TaskbarScrimViewController
@Mock lateinit var taskbarViewController: TaskbarViewController
@Mock lateinit var taskbarUnfoldAnimationController: TaskbarUnfoldAnimationController
@Mock lateinit var taskbarKeyguardController: TaskbarKeyguardController
@Mock lateinit var stashedHandleViewController: StashedHandleViewController
@Mock lateinit var taskbarStashController: TaskbarStashController
@Mock lateinit var taskbarEduController: TaskbarEduController
@Mock lateinit var taskbarAutohideSuspendController: TaskbarAutohideSuspendController
@Mock lateinit var taskbarPopupController: TaskbarPopupController
@Mock
lateinit var taskbarForceVisibleImmersiveController: TaskbarForceVisibleImmersiveController
@Mock
lateinit var taskbarAllAppsController: TaskbarAllAppsController
@Mock
lateinit var taskbarInsetsController: TaskbarInsetsController
@Mock
lateinit var voiceInteractionWindowController: VoiceInteractionWindowController
@Mock
lateinit var taskbarRecentAppsController: TaskbarRecentAppsController
@Mock
lateinit var taskbarTranslationController: TaskbarTranslationController
@Mock
lateinit var taskbarOverlayController: TaskbarOverlayController
@Mock lateinit var taskbarAllAppsController: TaskbarAllAppsController
@Mock lateinit var taskbarInsetsController: TaskbarInsetsController
@Mock lateinit var voiceInteractionWindowController: VoiceInteractionWindowController
@Mock lateinit var taskbarRecentAppsController: TaskbarRecentAppsController
@Mock lateinit var taskbarTranslationController: TaskbarTranslationController
@Mock lateinit var taskbarOverlayController: TaskbarOverlayController
@Mock lateinit var taskbarEduTooltipController: TaskbarEduTooltipController
lateinit var mTaskbarControllers: TaskbarControllers
@@ -85,16 +65,31 @@ abstract class TaskbarBaseTestCase {
* includes that method to allow mocking it.
*/
MockitoAnnotations.initMocks(this)
mTaskbarControllers = TaskbarControllers(
taskbarActivityContext, taskbarDragController, navButtonController,
navbarButtonsViewController, rotationButtonController, taskbarDragLayerController,
taskbarViewController, taskbarScrimViewController, taskbarUnfoldAnimationController,
taskbarKeyguardController, stashedHandleViewController, taskbarStashController,
taskbarEduController, taskbarAutohideSuspendController, taskbarPopupController,
taskbarForceVisibleImmersiveController, taskbarOverlayController,
taskbarAllAppsController, taskbarInsetsController,
voiceInteractionWindowController, taskbarTranslationController,
taskbarRecentAppsController
)
mTaskbarControllers =
TaskbarControllers(
taskbarActivityContext,
taskbarDragController,
navButtonController,
navbarButtonsViewController,
rotationButtonController,
taskbarDragLayerController,
taskbarViewController,
taskbarScrimViewController,
taskbarUnfoldAnimationController,
taskbarKeyguardController,
stashedHandleViewController,
taskbarStashController,
taskbarEduController,
taskbarAutohideSuspendController,
taskbarPopupController,
taskbarForceVisibleImmersiveController,
taskbarOverlayController,
taskbarAllAppsController,
taskbarInsetsController,
voiceInteractionWindowController,
taskbarTranslationController,
taskbarRecentAppsController,
taskbarEduTooltipController,
)
}
}
}

View File

@@ -44,6 +44,7 @@ public class OnboardingPrefs<T extends ActivityContext> {
public static final String TASKBAR_EDU_SEEN = "launcher.taskbar_edu_seen2";
public static final String ALL_APPS_VISITED_COUNT = "launcher.all_apps_visited_count";
public static final String QSB_SEARCH_ONBOARDING_CARD_DISMISSED = "launcher.qsb_edu_dismiss";
public static final String TASKBAR_EDU_TOOLTIP_STEP = "launcher.taskbar_edu_tooltip_step";
// When adding a new key, add it here as well, to be able to reset it from Developer Options.
public static final Map<String, String[]> ALL_PREF_KEYS = Map.of(
"All Apps Bounce", new String[] { HOME_BOUNCE_SEEN, HOME_BOUNCE_COUNT },
@@ -51,7 +52,7 @@ public class OnboardingPrefs<T extends ActivityContext> {
HOTSEAT_LONGPRESS_TIP_SEEN },
"Search Education", new String[] { SEARCH_KEYBOARD_EDU_SEEN, SEARCH_SNACKBAR_COUNT,
SEARCH_ONBOARDING_COUNT, QSB_SEARCH_ONBOARDING_CARD_DISMISSED},
"Taskbar Education", new String[] { TASKBAR_EDU_SEEN },
"Taskbar Education", new String[] { TASKBAR_EDU_SEEN, TASKBAR_EDU_TOOLTIP_STEP },
"All Apps Visited Count", new String[] {ALL_APPS_VISITED_COUNT}
);
@@ -76,7 +77,8 @@ public class OnboardingPrefs<T extends ActivityContext> {
HOTSEAT_DISCOVERY_TIP_COUNT,
SEARCH_SNACKBAR_COUNT,
SEARCH_ONBOARDING_COUNT,
ALL_APPS_VISITED_COUNT
ALL_APPS_VISITED_COUNT,
TASKBAR_EDU_TOOLTIP_STEP,
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventCountKey {}
@@ -91,6 +93,7 @@ public class OnboardingPrefs<T extends ActivityContext> {
// This is the sum of all onboarding cards. Currently there is only 1 card shown 3 times.
maxCounts.put(SEARCH_ONBOARDING_COUNT, 3);
maxCounts.put(ALL_APPS_VISITED_COUNT, 20);
maxCounts.put(TASKBAR_EDU_TOOLTIP_STEP, 2);
MAX_COUNTS = Collections.unmodifiableMap(maxCounts);
}