1 /*
2  * Copyright (C) 2022 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 
17 package com.android.launcher3.taskbar;
18 
19 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS;
20 import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS;
21 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
22 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
23 
24 import static com.android.launcher3.taskbar.NavbarButtonsViewController.ALPHA_INDEX_IMMERSIVE_MODE;
25 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
26 
27 import android.os.Bundle;
28 import android.os.Handler;
29 import android.os.Looper;
30 import android.view.MotionEvent;
31 import android.view.View;
32 
33 import com.android.launcher3.anim.AnimatedFloat;
34 import com.android.launcher3.compat.AccessibilityManagerCompat;
35 import com.android.launcher3.util.MultiPropertyFactory;
36 import com.android.launcher3.util.TouchController;
37 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
38 
39 /**
40  * Controller for taskbar when force visible in immersive mode is set.
41  */
42 public class TaskbarForceVisibleImmersiveController implements TouchController {
43     private static final int NAV_BAR_ICONS_DIM_ANIMATION_START_DELAY_MS = 4500;
44     private static final int NAV_BAR_ICONS_DIM_ANIMATION_DURATION_MS = 500;
45     private static final int NAV_BAR_ICONS_UNDIM_ANIMATION_DURATION_MS = 250;
46     private static final float NAV_BAR_ICONS_DIM_PCT = 0.15f;
47     private static final float NAV_BAR_ICONS_UNDIM_PCT = 1f;
48 
49     private final TaskbarActivityContext mContext;
50     private final Handler mHandler = new Handler(Looper.getMainLooper());
51     private final Runnable mDimmingRunnable = this::dimIcons;
52     private final Runnable mUndimmingRunnable = this::undimIcons;
53     private final AnimatedFloat mIconAlphaForDimming = new AnimatedFloat(
54             this::updateIconDimmingAlpha);
55     private final View.AccessibilityDelegate mKidsModeAccessibilityDelegate =
56             new View.AccessibilityDelegate() {
57                 @Override
58                 public boolean performAccessibilityAction(View host, int action, Bundle args) {
59                     if (action == ACTION_ACCESSIBILITY_FOCUS || action == ACTION_CLICK) {
60                         // Animate undimming of icons on an a11y event, followed by starting the
61                         // dimming animation (after its timeout has expired). Both can be called in
62                         // succession, as the playing of the two animations in a row is managed by
63                         // mHandler's message queue.
64                         startIconUndimming();
65                         startIconDimming();
66                     }
67                     return super.performAccessibilityAction(host, action, args);
68                 }
69             };
70 
71     // Initialized in init.
72     private TaskbarControllers mControllers;
73     private boolean mIsImmersiveMode;
74 
TaskbarForceVisibleImmersiveController(TaskbarActivityContext context)75     public TaskbarForceVisibleImmersiveController(TaskbarActivityContext context) {
76         mContext = context;
77     }
78 
79     /**
80      * Initialize controllers.
81      */
init(TaskbarControllers controllers)82     public void init(TaskbarControllers controllers) {
83         mControllers = controllers;
84     }
85 
86     /** Update values tracked via sysui flags. */
updateSysuiFlags(@ystemUiStateFlags long sysuiFlags)87     public void updateSysuiFlags(@SystemUiStateFlags long sysuiFlags) {
88         mIsImmersiveMode = (sysuiFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) == 0;
89         if (mContext.isNavBarForceVisible()) {
90             if (mIsImmersiveMode) {
91                 startIconDimming();
92             } else {
93                 startIconUndimming();
94             }
95             mControllers.navbarButtonsViewController.setHomeButtonAccessibilityDelegate(
96                     mKidsModeAccessibilityDelegate);
97             mControllers.navbarButtonsViewController.setBackButtonAccessibilityDelegate(
98                     mKidsModeAccessibilityDelegate);
99         } else {
100             mControllers.navbarButtonsViewController.setHomeButtonAccessibilityDelegate(null);
101             mControllers.navbarButtonsViewController.setBackButtonAccessibilityDelegate(null);
102         }
103     }
104 
105     /** Clean up animations. */
onDestroy()106     public void onDestroy() {
107         startIconUndimming();
108         mControllers.navbarButtonsViewController.setHomeButtonAccessibilityDelegate(null);
109         mControllers.navbarButtonsViewController.setBackButtonAccessibilityDelegate(null);
110     }
111 
startIconUndimming()112     private void startIconUndimming() {
113         mHandler.removeCallbacks(mDimmingRunnable);
114         mHandler.removeCallbacks(mUndimmingRunnable);
115         mHandler.post(mUndimmingRunnable);
116     }
117 
undimIcons()118     private void undimIcons() {
119         mIconAlphaForDimming.animateToValue(NAV_BAR_ICONS_UNDIM_PCT).setDuration(
120                 NAV_BAR_ICONS_UNDIM_ANIMATION_DURATION_MS).start();
121     }
122 
startIconDimming()123     private void startIconDimming() {
124         mHandler.removeCallbacks(mDimmingRunnable);
125         int accessibilityDimmingTimeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(
126                 mContext, NAV_BAR_ICONS_DIM_ANIMATION_START_DELAY_MS,
127                 (FLAG_CONTENT_ICONS | FLAG_CONTENT_CONTROLS));
128         mHandler.postDelayed(mDimmingRunnable, accessibilityDimmingTimeout);
129     }
130 
dimIcons()131     private void dimIcons() {
132         mIconAlphaForDimming.animateToValue(NAV_BAR_ICONS_DIM_PCT).setDuration(
133                 NAV_BAR_ICONS_DIM_ANIMATION_DURATION_MS).start();
134     }
135 
136     /**
137      * Returns whether the taskbar is always visible in immersive mode.
138      */
isNavbarShownInImmersiveMode()139     private boolean isNavbarShownInImmersiveMode() {
140         return mIsImmersiveMode && mContext.isNavBarForceVisible();
141     }
142 
updateIconDimmingAlpha()143     private void updateIconDimmingAlpha() {
144         if (mControllers == null || mControllers.navbarButtonsViewController == null) {
145             return;
146         }
147 
148         MultiPropertyFactory<View> ba =
149                 mControllers.navbarButtonsViewController.getBackButtonAlpha();
150         if (ba != null) {
151             ba.get(ALPHA_INDEX_IMMERSIVE_MODE).setValue(mIconAlphaForDimming.value);
152         }
153         MultiPropertyFactory<View> ha =
154                 mControllers.navbarButtonsViewController.getHomeButtonAlpha();
155         if (ba != null) {
156             ha.get(ALPHA_INDEX_IMMERSIVE_MODE).setValue(mIconAlphaForDimming.value);
157         }
158     }
159 
160     @Override
onControllerInterceptTouchEvent(MotionEvent ev)161     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
162         if (!isNavbarShownInImmersiveMode()) {
163             return false;
164         }
165         return onControllerTouchEvent(ev);
166     }
167 
168     @Override
onControllerTouchEvent(MotionEvent ev)169     public boolean onControllerTouchEvent(MotionEvent ev) {
170         switch (ev.getAction()) {
171             case MotionEvent.ACTION_DOWN:
172                 startIconUndimming();
173                 break;
174             case MotionEvent.ACTION_UP:
175             case MotionEvent.ACTION_CANCEL:
176                 startIconDimming();
177                 break;
178         }
179         return false;
180     }
181 }
182