• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
19 
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.ValueAnimator;
23 import android.content.SharedPreferences;
24 import android.content.res.Resources;
25 import android.graphics.Outline;
26 import android.graphics.Rect;
27 import android.view.View;
28 import android.view.ViewOutlineProvider;
29 
30 import com.android.launcher3.DeviceProfile;
31 import com.android.launcher3.LauncherPrefs;
32 import com.android.launcher3.R;
33 import com.android.launcher3.anim.AnimatedFloat;
34 import com.android.launcher3.anim.RevealOutlineAnimation;
35 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
36 import com.android.launcher3.util.DisplayController;
37 import com.android.launcher3.util.Executors;
38 import com.android.launcher3.util.MultiPropertyFactory;
39 import com.android.launcher3.util.MultiValueAlpha;
40 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
41 
42 import java.io.PrintWriter;
43 
44 /**
45  * Handles properties/data collection, then passes the results to our stashed handle View to render.
46  */
47 public class StashedHandleViewController implements TaskbarControllers.LoggableTaskbarController {
48 
49     public static final int ALPHA_INDEX_STASHED = 0;
50     public static final int ALPHA_INDEX_HOME_DISABLED = 1;
51     public static final int ALPHA_INDEX_ASSISTANT_INVOKED = 2;
52     public static final int ALPHA_INDEX_HIDDEN_WHILE_DREAMING = 3;
53     private static final int NUM_ALPHA_CHANNELS = 4;
54 
55     /**
56      * The SharedPreferences key for whether the stashed handle region is dark.
57      */
58     private static final String SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY =
59             "stashed_handle_region_is_dark";
60 
61     private final TaskbarActivityContext mActivity;
62     private final SharedPreferences mPrefs;
63     private final StashedHandleView mStashedHandleView;
64     private int mStashedHandleWidth;
65     private final int mStashedHandleHeight;
66     private RegionSamplingHelper mRegionSamplingHelper;
67     private final MultiValueAlpha mTaskbarStashedHandleAlpha;
68     private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat(
69             this::updateStashedHandleHintScale);
70 
71     // Initialized in init.
72     private TaskbarControllers mControllers;
73     private int mTaskbarSize;
74 
75     // The bounds we want to clip to in the settled state when showing the stashed handle.
76     private final Rect mStashedHandleBounds = new Rect();
77     private float mStashedHandleRadius;
78 
79     // When the reveal animation is cancelled, we can assume it's about to create a new animation,
80     // which should start off at the same point the cancelled one left off.
81     private float mStartProgressForNextRevealAnim;
82     private boolean mWasLastRevealAnimReversed;
83 
84     // States that affect whether region sampling is enabled or not
85     private boolean mIsStashed;
86     private boolean mTaskbarHidden;
87 
88     private float mTranslationYForSwipe;
89     private float mTranslationYForStash;
90 
StashedHandleViewController(TaskbarActivityContext activity, StashedHandleView stashedHandleView)91     public StashedHandleViewController(TaskbarActivityContext activity,
92             StashedHandleView stashedHandleView) {
93         mActivity = activity;
94         mPrefs = LauncherPrefs.getPrefs(mActivity);
95         mStashedHandleView = stashedHandleView;
96         mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, NUM_ALPHA_CHANNELS);
97         mTaskbarStashedHandleAlpha.setUpdateVisibility(true);
98         mStashedHandleView.updateHandleColor(
99                 mPrefs.getBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, false),
100                 false /* animate */);
101         final Resources resources = mActivity.getResources();
102         mStashedHandleHeight = resources.getDimensionPixelSize(
103                 R.dimen.taskbar_stashed_handle_height);
104     }
105 
init(TaskbarControllers controllers)106     public void init(TaskbarControllers controllers) {
107         mControllers = controllers;
108         DeviceProfile deviceProfile = mActivity.getDeviceProfile();
109         Resources resources = mActivity.getResources();
110         if (isPhoneGestureNavMode(mActivity.getDeviceProfile())) {
111             mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_size);
112             mStashedHandleWidth =
113                     resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
114         } else {
115             mTaskbarSize = deviceProfile.taskbarHeight;
116             mStashedHandleWidth = resources
117                     .getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
118         }
119         int taskbarBottomMargin = deviceProfile.taskbarBottomMargin;
120         mStashedHandleView.getLayoutParams().height = mTaskbarSize + taskbarBottomMargin;
121 
122         mTaskbarStashedHandleAlpha.get(ALPHA_INDEX_STASHED).setValue(
123                 isPhoneGestureNavMode(deviceProfile) ? 1 : 0);
124         mTaskbarStashedHandleHintScale.updateValue(1f);
125 
126         final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight();
127         mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
128             @Override
129             public void getOutline(View view, Outline outline) {
130                 final int stashedCenterX = view.getWidth() / 2;
131                 final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
132                 mStashedHandleBounds.set(
133                         stashedCenterX - mStashedHandleWidth / 2,
134                         stashedCenterY - mStashedHandleHeight / 2,
135                         stashedCenterX + mStashedHandleWidth / 2,
136                         stashedCenterY + mStashedHandleHeight / 2);
137                 mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
138                 mStashedHandleRadius = view.getHeight() / 2f;
139                 outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius);
140             }
141         });
142 
143         mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> {
144             final int stashedCenterX = view.getWidth() / 2;
145             final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
146 
147             view.setPivotX(stashedCenterX);
148             view.setPivotY(stashedCenterY);
149         });
150         initRegionSampler();
151         if (isPhoneGestureNavMode(deviceProfile)) {
152             onIsStashedChanged(true);
153         }
154     }
155 
156     /**
157      * Returns the stashed handle bounds.
158      * @param out The destination rect.
159      */
getStashedHandleBounds(Rect out)160     public void getStashedHandleBounds(Rect out) {
161         out.set(mStashedHandleBounds);
162     }
163 
initRegionSampler()164     private void initRegionSampler() {
165         mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
166                 new RegionSamplingHelper.SamplingCallback() {
167                     @Override
168                     public void onRegionDarknessChanged(boolean isRegionDark) {
169                         mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */);
170                         mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY,
171                                 isRegionDark).apply();
172                     }
173 
174                     @Override
175                     public Rect getSampledRegion(View sampledView) {
176                         return mStashedHandleView.getSampledRegion();
177                     }
178                 }, Executors.UI_HELPER_EXECUTOR);
179     }
180 
181 
onDestroy()182     public void onDestroy() {
183         mRegionSamplingHelper.stopAndDestroy();
184         mRegionSamplingHelper = null;
185     }
186 
isPhoneGestureNavMode(DeviceProfile deviceProfile)187     private boolean isPhoneGestureNavMode(DeviceProfile deviceProfile) {
188         return TaskbarManager.isPhoneMode(deviceProfile) && !mActivity.isThreeButtonNav();
189     }
190 
getStashedHandleAlpha()191     public MultiPropertyFactory<View> getStashedHandleAlpha() {
192         return mTaskbarStashedHandleAlpha;
193     }
194 
getStashedHandleHintScale()195     public AnimatedFloat getStashedHandleHintScale() {
196         return mTaskbarStashedHandleHintScale;
197     }
198 
199     /**
200      * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle
201      * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape
202      * morphs into the size of where the taskbar icons will be.
203      */
createRevealAnimToIsStashed(boolean isStashed)204     public Animator createRevealAnimToIsStashed(boolean isStashed) {
205         Rect visualBounds = new Rect(mControllers.taskbarViewController.getIconLayoutBounds());
206         float startRadius = mStashedHandleRadius;
207 
208         if (DisplayController.isTransientTaskbar(mActivity)) {
209             // Account for the full visual height of the transient taskbar.
210             int heightDiff = (mTaskbarSize - visualBounds.height()) / 2;
211             visualBounds.top -= heightDiff;
212             visualBounds.bottom += heightDiff;
213 
214             startRadius = visualBounds.height() / 2f;
215         }
216 
217         final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
218                 startRadius, mStashedHandleRadius, visualBounds, mStashedHandleBounds);
219 
220         boolean isReversed = !isStashed;
221         boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
222         mWasLastRevealAnimReversed = isReversed;
223         if (changingDirection) {
224             mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
225         }
226 
227         ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView,
228                 isReversed, mStartProgressForNextRevealAnim);
229         revealAnim.addListener(new AnimatorListenerAdapter() {
230             @Override
231             public void onAnimationEnd(Animator animation) {
232                 mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction();
233             }
234         });
235         return revealAnim;
236     }
237 
238     /** Called when taskbar is stashed or unstashed. */
onIsStashedChanged(boolean isStashed)239     public void onIsStashedChanged(boolean isStashed) {
240         mIsStashed = isStashed;
241         updateRegionSamplingWindowVisibility();
242         if (isStashed) {
243             mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
244             mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
245         } else {
246             mRegionSamplingHelper.stop();
247         }
248     }
249 
updateStashedHandleHintScale()250     protected void updateStashedHandleHintScale() {
251         mStashedHandleView.setScaleX(mTaskbarStashedHandleHintScale.value);
252         mStashedHandleView.setScaleY(mTaskbarStashedHandleHintScale.value);
253     }
254 
255     /**
256      * Sets the translation of the stashed handle during the swipe up gesture.
257      */
setTranslationYForSwipe(float transY)258     protected void setTranslationYForSwipe(float transY) {
259         mTranslationYForSwipe = transY;
260         updateTranslationY();
261     }
262 
263     /**
264      * Sets the translation of the stashed handle during the spring on stash animation.
265      */
setTranslationYForStash(float transY)266     protected void setTranslationYForStash(float transY) {
267         mTranslationYForStash = transY;
268         updateTranslationY();
269     }
270 
updateTranslationY()271     private void updateTranslationY() {
272         mStashedHandleView.setTranslationY(mTranslationYForSwipe + mTranslationYForStash);
273     }
274 
275     /**
276      * Should be called when the home button is disabled, so we can hide this handle as well.
277      */
setIsHomeButtonDisabled(boolean homeDisabled)278     public void setIsHomeButtonDisabled(boolean homeDisabled) {
279         mTaskbarStashedHandleAlpha.get(ALPHA_INDEX_HOME_DISABLED).setValue(
280                 homeDisabled ? 0 : 1);
281     }
282 
updateStateForSysuiFlags(int systemUiStateFlags)283     public void updateStateForSysuiFlags(int systemUiStateFlags) {
284         mTaskbarHidden = (systemUiStateFlags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0;
285         updateRegionSamplingWindowVisibility();
286     }
287 
updateRegionSamplingWindowVisibility()288     private void updateRegionSamplingWindowVisibility() {
289         mRegionSamplingHelper.setWindowVisible(mIsStashed && !mTaskbarHidden);
290     }
291 
isStashedHandleVisible()292     public boolean isStashedHandleVisible() {
293         return mStashedHandleView.getVisibility() == View.VISIBLE;
294     }
295 
296     @Override
dumpLogs(String prefix, PrintWriter pw)297     public void dumpLogs(String prefix, PrintWriter pw) {
298         pw.println(prefix + "StashedHandleViewController:");
299 
300         pw.println(prefix + "\tisStashedHandleVisible=" + isStashedHandleVisible());
301         pw.println(prefix + "\tmStashedHandleWidth=" + mStashedHandleWidth);
302         pw.println(prefix + "\tmStashedHandleHeight=" + mStashedHandleHeight);
303         mRegionSamplingHelper.dump(prefix, pw);
304     }
305 }
306