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