Merge "Fix Taskbar Edu accessibility anmimation" into main

This commit is contained in:
Jagrut Desai
2025-02-24 13:27:53 -08:00
committed by Android (Google) Code Review
5 changed files with 149 additions and 38 deletions
@@ -56,7 +56,6 @@ import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVIT
import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE;
import static com.android.launcher3.util.DisplayController.isTransientTaskbar;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.ORDERED_BG_EXECUTOR;
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -79,7 +78,6 @@ import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Point;
@@ -94,7 +92,6 @@ import android.os.Looper;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.util.Pair;
import android.util.Size;
import android.view.CrossWindowBlurListeners;
@@ -249,16 +246,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
// {@link TaskRestartedDuringLaunchListener}, and remove them on activity destroy.
private final List<TaskRestartedDuringLaunchListener> mRegisteredTaskStackChangeListener =
new ArrayList<>();
private final ContentObserver mAnimationRemovalObserver = new ContentObserver(
ORDERED_BG_EXECUTOR.getHandler()) {
@Override
public void onChange(boolean selfChange) {
mAreAnimationsEnabled = Global.getFloat(mLauncher.getContentResolver(),
Global.ANIMATOR_DURATION_SCALE, 1f) > 0
|| Global.getFloat(mLauncher.getContentResolver(),
Global.TRANSITION_ANIMATION_SCALE, 1f) > 0;
}
};
private DeviceProfile mDeviceProfile;
@@ -287,7 +274,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
// Pairs of window starting type and starting window background color for starting tasks
// Will never be larger than MAX_NUM_TASKS
private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams;
private boolean mAreAnimationsEnabled = true;
private final Interpolator mOpeningXInterpolator;
private final Interpolator mOpeningInterpolator;
@@ -298,7 +284,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
mHandler = new Handler(Looper.getMainLooper());
mDeviceProfile = mLauncher.getDeviceProfile();
mBackAnimationController = new LauncherBackAnimationController(mLauncher, this);
checkAndMonitorIfAnimationsAreEnabled();
Resources res = mLauncher.getResources();
mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y);
@@ -1220,8 +1205,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
unregisterRemoteTransitions();
mLauncher.removeOnDeviceProfileChangeListener(this);
SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null);
ORDERED_BG_EXECUTOR.execute(() -> mLauncher.getContentResolver()
.unregisterContentObserver(mAnimationRemovalObserver));
if (BuildConfig.IS_STUDIO_BUILD && !mRegisteredTaskStackChangeListener.isEmpty()) {
throw new IllegalStateException("Failed to run onEndCallback created from"
+ " getActivityLaunchOptions()");
@@ -1275,17 +1258,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
}
}
private void checkAndMonitorIfAnimationsAreEnabled() {
ORDERED_BG_EXECUTOR.execute(() -> {
mAnimationRemovalObserver.onChange(true);
mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
Global.ANIMATOR_DURATION_SCALE), false, mAnimationRemovalObserver);
mLauncher.getContentResolver().registerContentObserver(Global.getUriFor(
Global.TRANSITION_ANIMATION_SCALE), false, mAnimationRemovalObserver);
});
}
private boolean launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode) {
for (RemoteAnimationTarget target : targets) {
if (target.mode == mode && target.taskInfo != null
@@ -1421,7 +1393,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener
(LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
isTransluscent, fallbackBackgroundColor);
} else if (launcherView != null && mAreAnimationsEnabled) {
} else if (launcherView != null && !RemoveAnimationSettingsTracker.INSTANCE.get(
mLauncher).isRemoveAnimationEnabled()) {
floatingIconView = getFloatingIconView(mLauncher, launcherView, null,
mLauncher.getTaskbarUIController() == null
? null
@@ -39,6 +39,7 @@ import androidx.core.view.updateLayoutParams
import com.airbnb.lottie.LottieAnimationView
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.R
import com.android.launcher3.RemoveAnimationSettingsTracker
import com.android.launcher3.Utilities
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
@@ -128,6 +129,26 @@ open class TaskbarEduTooltipController(context: Context) :
activityContext.dragLayer.post { maybeShowSearchEdu() }
}
/**
* Turns off auto play of lottie animations if user has opted to remove animation else attaches
* click listener to allow user to play or pause animations.
*/
fun handleEduAnimations(animationViews: List<LottieAnimationView>) {
for (animationView in animationViews) {
if (
RemoveAnimationSettingsTracker.INSTANCE.get(animationView.context)
.isRemoveAnimationEnabled()
) {
animationView.pauseAnimation()
} else {
animationView.setOnClickListener {
if (animationView.isAnimating) animationView.pauseAnimation()
else animationView.playAnimation()
}
}
}
}
/** Shows swipe EDU tooltip if it is the current [tooltipStep]. */
fun maybeShowSwipeEdu() {
if (
@@ -145,7 +166,9 @@ open class TaskbarEduTooltipController(context: Context) :
requireViewById(R.id.taskbar_edu_title),
TypefaceUtils.FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED,
)
requireViewById<LottieAnimationView>(R.id.swipe_animation).supportLightTheme()
val swipeAnimation = requireViewById<LottieAnimationView>(R.id.swipe_animation)
swipeAnimation.supportLightTheme()
handleEduAnimations(listOf(swipeAnimation))
show()
}
}
@@ -174,6 +197,7 @@ open class TaskbarEduTooltipController(context: Context) :
splitscreenAnim.supportLightTheme()
suggestionsAnim.supportLightTheme()
pinningAnim.supportLightTheme()
handleEduAnimations(listOf(splitscreenAnim, suggestionsAnim, pinningAnim))
if (DisplayController.isTransientTaskbar(activityContext)) {
splitscreenAnim.setAnimation(R.raw.taskbar_edu_splitscreen_transient)
suggestionsAnim.setAnimation(R.raw.taskbar_edu_suggestions_transient)
@@ -249,9 +273,6 @@ open class TaskbarEduTooltipController(context: Context) :
tooltip?.run {
allowTouchDismissal = true
requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
.supportLightTheme()
TypefaceUtils.setTypeface(
requireViewById(R.id.taskbar_edu_title),
TypefaceUtils.FONT_FAMILY_HEADLINE_SMALL_EMPHASIZED,
@@ -261,6 +282,10 @@ open class TaskbarEduTooltipController(context: Context) :
TypefaceUtils.FONT_FAMILY_BODY_MEDIUM_BASELINE,
)
val pinningAnim =
requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
pinningAnim.supportLightTheme()
handleEduAnimations(listOf(pinningAnim))
updateLayoutParams<BaseDragLayer.LayoutParams> {
if (DisplayController.isTransientTaskbar(activityContext)) {
bottomMargin += activityContext.deviceProfile.taskbarHeight
@@ -304,7 +329,9 @@ open class TaskbarEduTooltipController(context: Context) :
inflateTooltip(R.layout.taskbar_edu_search)
tooltip?.run {
allowTouchDismissal = true
requireViewById<LottieAnimationView>(R.id.search_edu_animation).supportLightTheme()
val searchEdu = requireViewById<LottieAnimationView>(R.id.search_edu_animation)
searchEdu.supportLightTheme()
handleEduAnimations(listOf(searchEdu))
val eduSubtitle: TextView = requireViewById(R.id.search_edu_text)
TypefaceUtils.setTypeface(
+3 -3
View File
@@ -17,6 +17,7 @@
package com.android.launcher3;
import static com.android.app.animation.Interpolators.SCROLL;
import static com.android.launcher3.RemoveAnimationSettingsTracker.WINDOW_ANIMATION_SCALE_URI;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
import static com.android.launcher3.testing.shared.TestProtocol.SCROLL_FINISHED_MESSAGE;
@@ -33,7 +34,6 @@ import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Bundle;
import android.provider.Settings;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InputDevice;
@@ -1756,8 +1756,8 @@ public abstract class PagedView<T extends View & PageIndicator> extends ViewGrou
}
if (FeatureFlags.IS_STUDIO_BUILD && !Utilities.isRunningInTestHarness()) {
duration *= Settings.Global.getFloat(getContext().getContentResolver(),
Settings.Global.WINDOW_ANIMATION_SCALE, 1);
duration *= RemoveAnimationSettingsTracker.INSTANCE.get(getContext()).getValue(
WINDOW_ANIMATION_SCALE_URI);
}
whichPage = validateNewPage(whichPage);
@@ -0,0 +1,110 @@
/*
* Copyright (C) 2025 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
import android.content.Context
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.provider.Settings
import android.provider.Settings.Global.ANIMATOR_DURATION_SCALE
import android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE
import android.provider.Settings.Global.WINDOW_ANIMATION_SCALE
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.dagger.LauncherAppComponent
import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.util.DaggerSingletonObject
import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
/** Tracker Class for when user turns on/off remove animation setting. */
@LauncherAppSingleton
class RemoveAnimationSettingsTracker
@Inject
constructor(@ApplicationContext val context: Context, tracker: DaggerSingletonTracker) :
ContentObserver(Handler(Looper.getMainLooper())) {
private val contentResolver = context.contentResolver
/** Caches the last seen value for registered keys. */
private val cache: MutableMap<Uri, Float> = ConcurrentHashMap()
init {
UI_HELPER_EXECUTOR.execute {
contentResolver.registerContentObserver(WINDOW_ANIMATION_SCALE_URI, false, this)
contentResolver.registerContentObserver(TRANSITION_ANIMATION_SCALE_URI, false, this)
contentResolver.registerContentObserver(ANIMATOR_DURATION_SCALE_URI, false, this)
}
tracker.addCloseable {
UI_HELPER_EXECUTOR.execute { contentResolver.unregisterContentObserver(this) }
}
}
/**
* Returns the value for this classes key from the cache. If not in cache, will call
* [updateValue] to fetch.
*/
fun getValue(uri: Uri): Float {
return getValue(uri, 1f)
}
/**
* Returns the value for this classes key from the cache. If not in cache, will call
* [getValueFromSettingsGlobal] to fetch.
*/
private fun getValue(uri: Uri, defaultValue: Float): Float {
return cache.computeIfAbsent(uri) { getValueFromSettingsGlobal(uri, defaultValue) }
}
/** Returns if user has opted into having no animation on their device. */
fun isRemoveAnimationEnabled(): Boolean {
return getValue(WINDOW_ANIMATION_SCALE_URI) == 0f &&
getValue(TRANSITION_ANIMATION_SCALE_URI) == 0f &&
getValue(ANIMATOR_DURATION_SCALE_URI) == 0f
}
override fun onChange(selfChange: Boolean, uri: Uri?) {
if (uri == null) return
updateValue(uri)
}
private fun getValueFromSettingsGlobal(uri: Uri, defaultValue: Float = 1f): Float {
return Settings.Global.getFloat(contentResolver, uri.lastPathSegment, defaultValue)
}
private fun updateValue(uri: Uri, defaultValue: Float = 1f) {
val newValue = getValueFromSettingsGlobal(uri, defaultValue)
cache[uri] = newValue
}
companion object {
@JvmField
val INSTANCE =
DaggerSingletonObject(LauncherAppComponent::getRemoveAnimationSettingsTracker)
@JvmField
val WINDOW_ANIMATION_SCALE_URI: Uri = Settings.Global.getUriFor(WINDOW_ANIMATION_SCALE)
@JvmField
val TRANSITION_ANIMATION_SCALE_URI: Uri =
Settings.Global.getUriFor(TRANSITION_ANIMATION_SCALE)
@JvmField
val ANIMATOR_DURATION_SCALE_URI: Uri = Settings.Global.getUriFor(ANIMATOR_DURATION_SCALE)
}
}
@@ -23,6 +23,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.RemoveAnimationSettingsTracker;
import com.android.launcher3.graphics.GridCustomizationsProxy;
import com.android.launcher3.graphics.ThemeManager;
import com.android.launcher3.icons.LauncherIcons.IconPool;
@@ -80,8 +81,8 @@ public interface LauncherBaseAppComponent {
LockedUserState getLockedUserState();
InvariantDeviceProfile getIDP();
IconPool getIconPool();
RemoveAnimationSettingsTracker getRemoveAnimationSettingsTracker();
LauncherAppState getLauncherAppState();
GridCustomizationsProxy getGridCustomizationsProxy();
/** Builder for LauncherBaseAppComponent. */