1 /* 2 * Copyright (C) 2023 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.launcher3.taskbar 17 18 import android.animation.AnimatorSet 19 import android.annotation.SuppressLint 20 import android.view.View 21 import androidx.annotation.VisibleForTesting 22 import androidx.core.animation.doOnEnd 23 import com.android.app.animation.Interpolators 24 import com.android.launcher3.LauncherPrefs 25 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING 26 import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING_IN_DESKTOP_MODE 27 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE 28 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN 29 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_PINNED 30 import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_UNPINNED 31 import com.android.launcher3.taskbar.TaskbarDividerPopupView.Companion.createAndPopulate 32 import java.io.PrintWriter 33 import kotlin.jvm.optionals.getOrNull 34 35 /** Controls taskbar pinning through a popup view. */ 36 class TaskbarPinningController(private val context: TaskbarActivityContext) : 37 TaskbarControllers.LoggableTaskbarController { 38 39 private lateinit var controllers: TaskbarControllers 40 private lateinit var taskbarSharedState: TaskbarSharedState 41 private lateinit var launcherPrefs: LauncherPrefs 42 private val statsLogManager = context.statsLogManager 43 @VisibleForTesting var isAnimatingTaskbarPinning = false 44 @VisibleForTesting lateinit var onCloseCallback: (preferenceChanged: Boolean) -> Unit 45 46 @SuppressLint("VisibleForTests") initnull47 fun init(taskbarControllers: TaskbarControllers, sharedState: TaskbarSharedState) { 48 controllers = taskbarControllers 49 taskbarSharedState = sharedState 50 launcherPrefs = context.launcherPrefs 51 onCloseCallback = 52 fun(didPreferenceChange: Boolean) { 53 statsLogManager.logger().log(LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE) 54 context.dragLayer.post { context.onPopupVisibilityChanged(false) } 55 56 if (!didPreferenceChange) { 57 return 58 } 59 val shouldPinTaskbar = 60 if ( 61 controllers.taskbarDesktopModeController.isInDesktopModeAndNotInOverview( 62 context.displayId 63 ) 64 ) { 65 !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE) 66 } else { 67 !launcherPrefs.get(TASKBAR_PINNING) 68 } 69 70 val animateToValue = 71 if (shouldPinTaskbar) { 72 statsLogManager.logger().log(LAUNCHER_TASKBAR_PINNED) 73 PINNING_PERSISTENT 74 } else { 75 statsLogManager.logger().log(LAUNCHER_TASKBAR_UNPINNED) 76 PINNING_TRANSIENT 77 } 78 79 taskbarSharedState.taskbarWasPinned = animateToValue == PINNING_TRANSIENT 80 animateTaskbarPinning(animateToValue) 81 } 82 } 83 showPinningViewnull84 fun showPinningView(view: View, horizontalPosition: Float = -1f) { 85 context.isTaskbarWindowFullscreen = true 86 view.post { 87 val popupView = getPopupView(view, horizontalPosition) 88 popupView.requestFocus() 89 popupView.onCloseCallback = onCloseCallback 90 context.onPopupVisibilityChanged(true) 91 popupView.show() 92 statsLogManager.logger().log(LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN) 93 } 94 } 95 96 @VisibleForTesting getPopupViewnull97 fun getPopupView(view: View, horizontalPosition: Float = -1f): TaskbarDividerPopupView<*> { 98 return createAndPopulate(view, context, horizontalPosition) 99 } 100 101 @VisibleForTesting animateTaskbarPinningnull102 fun animateTaskbarPinning(animateToValue: Float) { 103 val taskbarViewController = controllers.taskbarViewController 104 val animatorSet = 105 getAnimatorSetForTaskbarPinningAnimation(animateToValue).apply { 106 doOnEnd { recreateTaskbarAndUpdatePinningValue() } 107 duration = PINNING_ANIMATION_DURATION 108 } 109 controllers.taskbarOverlayController.hideWindow() 110 updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(true) 111 taskbarViewController.animateAwayNotificationDotsDuringTaskbarPinningAnimation() 112 animatorSet.start() 113 } 114 115 @VisibleForTesting getAnimatorSetForTaskbarPinningAnimationnull116 fun getAnimatorSetForTaskbarPinningAnimation(animateToValue: Float): AnimatorSet { 117 val animatorSet = AnimatorSet() 118 val taskbarViewController = controllers.taskbarViewController 119 val dragLayerController = controllers.taskbarDragLayerController 120 121 animatorSet.playTogether( 122 dragLayerController.taskbarBackgroundProgress.animateToValue(animateToValue), 123 taskbarViewController.taskbarIconTranslationYForPinning.animateToValue(animateToValue), 124 taskbarViewController.taskbarIconScaleForPinning.animateToValue(animateToValue), 125 taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue), 126 ) 127 controllers.bubbleControllers.getOrNull()?.bubbleBarViewController?.let { 128 // if bubble bar is not visible no need to add it`s animations 129 if (!it.isBubbleBarVisible) return@let 130 animatorSet.playTogether(it.bubbleBarPinning.animateToValue(animateToValue)) 131 } 132 animatorSet.interpolator = Interpolators.EMPHASIZED 133 return animatorSet 134 } 135 updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayernull136 private fun updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(isAnimating: Boolean) { 137 isAnimatingTaskbarPinning = isAnimating 138 context.dragLayer.setAnimatingTaskbarPinning(isAnimating) 139 } 140 141 @VisibleForTesting recreateTaskbarAndUpdatePinningValuenull142 fun recreateTaskbarAndUpdatePinningValue() { 143 updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(false) 144 if ( 145 controllers.taskbarDesktopModeController.isInDesktopModeAndNotInOverview( 146 context.displayId 147 ) 148 ) { 149 launcherPrefs.put( 150 TASKBAR_PINNING_IN_DESKTOP_MODE, 151 !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE), 152 ) 153 } else { 154 launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING)) 155 } 156 } 157 dumpLogsnull158 override fun dumpLogs(prefix: String, pw: PrintWriter) { 159 pw.println(prefix + "TaskbarPinningController:") 160 pw.println("$prefix\tisAnimatingTaskbarPinning=$isAnimatingTaskbarPinning") 161 pw.println("$prefix\tTASKBAR_PINNING shared pref =" + launcherPrefs.get(TASKBAR_PINNING)) 162 pw.println( 163 "$prefix\tTASKBAR_PINNING_IN_DESKTOP_MODE shared pref in desktop mode =" + 164 launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE) 165 ) 166 } 167 168 companion object { 169 const val PINNING_PERSISTENT = 1f 170 const val PINNING_TRANSIENT = 0f 171 const val PINNING_ANIMATION_DURATION = 600L 172 } 173 } 174