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 
34 /** Controls taskbar pinning through a popup view. */
35 class TaskbarPinningController(
36     private val context: TaskbarActivityContext,
37     private val isInDesktopModeProvider: () -> Boolean,
38 ) : TaskbarControllers.LoggableTaskbarController {
39 
40     private lateinit var controllers: TaskbarControllers
41     private lateinit var taskbarSharedState: TaskbarSharedState
42     private lateinit var launcherPrefs: LauncherPrefs
43     private val statsLogManager = context.statsLogManager
44     @VisibleForTesting var isAnimatingTaskbarPinning = false
45     @VisibleForTesting lateinit var onCloseCallback: (preferenceChanged: Boolean) -> Unit
46 
47     @SuppressLint("VisibleForTests")
initnull48     fun init(taskbarControllers: TaskbarControllers, sharedState: TaskbarSharedState) {
49         controllers = taskbarControllers
50         taskbarSharedState = sharedState
51         launcherPrefs = context.launcherPrefs
52         onCloseCallback =
53             fun(didPreferenceChange: Boolean) {
54                 statsLogManager.logger().log(LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE)
55                 context.dragLayer.post { context.onPopupVisibilityChanged(false) }
56 
57                 if (!didPreferenceChange) {
58                     return
59                 }
60                 val shouldPinTaskbar =
61                     if (isInDesktopModeProvider()) {
62                         !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
63                     } else {
64                         !launcherPrefs.get(TASKBAR_PINNING)
65                     }
66 
67                 val animateToValue =
68                     if (shouldPinTaskbar) {
69                         statsLogManager.logger().log(LAUNCHER_TASKBAR_PINNED)
70                         PINNING_PERSISTENT
71                     } else {
72                         statsLogManager.logger().log(LAUNCHER_TASKBAR_UNPINNED)
73                         PINNING_TRANSIENT
74                     }
75 
76                 taskbarSharedState.taskbarWasPinned = animateToValue == PINNING_TRANSIENT
77                 animateTaskbarPinning(animateToValue)
78             }
79     }
80 
showPinningViewnull81     fun showPinningView(view: View) {
82         context.isTaskbarWindowFullscreen = true
83         view.post {
84             val popupView = getPopupView(view)
85             popupView.requestFocus()
86             popupView.onCloseCallback = onCloseCallback
87             context.onPopupVisibilityChanged(true)
88             popupView.show()
89             statsLogManager.logger().log(LAUNCHER_TASKBAR_DIVIDER_MENU_OPEN)
90         }
91     }
92 
93     @VisibleForTesting
getPopupViewnull94     fun getPopupView(view: View): TaskbarDividerPopupView<*> {
95         return createAndPopulate(view, context)
96     }
97 
98     @VisibleForTesting
animateTaskbarPinningnull99     fun animateTaskbarPinning(animateToValue: Float) {
100         val taskbarViewController = controllers.taskbarViewController
101         val animatorSet =
102             getAnimatorSetForTaskbarPinningAnimation(animateToValue).apply {
103                 doOnEnd { recreateTaskbarAndUpdatePinningValue() }
104                 duration = PINNING_ANIMATION_DURATION
105             }
106         controllers.taskbarOverlayController.hideWindow()
107         updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(true)
108         taskbarViewController.animateAwayNotificationDotsDuringTaskbarPinningAnimation()
109         animatorSet.start()
110     }
111 
112     @VisibleForTesting
getAnimatorSetForTaskbarPinningAnimationnull113     fun getAnimatorSetForTaskbarPinningAnimation(animateToValue: Float): AnimatorSet {
114         val animatorSet = AnimatorSet()
115         val taskbarViewController = controllers.taskbarViewController
116         val dragLayerController = controllers.taskbarDragLayerController
117 
118         animatorSet.playTogether(
119             dragLayerController.taskbarBackgroundProgress.animateToValue(animateToValue),
120             taskbarViewController.taskbarIconTranslationYForPinning.animateToValue(animateToValue),
121             taskbarViewController.taskbarIconScaleForPinning.animateToValue(animateToValue),
122             taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue)
123         )
124 
125         animatorSet.interpolator = Interpolators.EMPHASIZED
126         return animatorSet
127     }
128 
updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayernull129     private fun updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(isAnimating: Boolean) {
130         isAnimatingTaskbarPinning = isAnimating
131         context.dragLayer.setAnimatingTaskbarPinning(isAnimating)
132     }
133 
134     @VisibleForTesting
recreateTaskbarAndUpdatePinningValuenull135     fun recreateTaskbarAndUpdatePinningValue() {
136         updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(false)
137         if (isInDesktopModeProvider()) {
138             launcherPrefs.put(
139                 TASKBAR_PINNING_IN_DESKTOP_MODE,
140                 !launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
141             )
142         } else {
143             launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
144         }
145     }
146 
dumpLogsnull147     override fun dumpLogs(prefix: String, pw: PrintWriter) {
148         pw.println(prefix + "TaskbarPinningController:")
149         pw.println("$prefix\tisAnimatingTaskbarPinning=$isAnimatingTaskbarPinning")
150         pw.println("$prefix\tTASKBAR_PINNING shared pref =" + launcherPrefs.get(TASKBAR_PINNING))
151         pw.println(
152             "$prefix\tTASKBAR_PINNING_IN_DESKTOP_MODE shared pref in desktop mode =" +
153                 launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
154         )
155     }
156 
157     companion object {
158         const val PINNING_PERSISTENT = 1f
159         const val PINNING_TRANSIENT = 0f
160         const val PINNING_ANIMATION_DURATION = 600L
161     }
162 }
163