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 android.animation.Animator;
19 import android.animation.AnimatorListenerAdapter;
20 import android.animation.ValueAnimator;
21 import android.content.SharedPreferences;
22 import android.content.res.Resources;
23 import android.graphics.Outline;
24 import android.graphics.Rect;
25 import android.view.View;
26 import android.view.ViewOutlineProvider;
27 
28 import com.android.launcher3.R;
29 import com.android.launcher3.Utilities;
30 import com.android.launcher3.anim.RevealOutlineAnimation;
31 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
32 import com.android.launcher3.util.Executors;
33 import com.android.launcher3.util.MultiValueAlpha;
34 import com.android.quickstep.AnimatedFloat;
35 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
36 
37 /**
38  * Handles properties/data collection, then passes the results to our stashed handle View to render.
39  */
40 public class StashedHandleViewController {
41 
42     public static final int ALPHA_INDEX_STASHED = 0;
43     public static final int ALPHA_INDEX_HOME_DISABLED = 1;
44     private static final int NUM_ALPHA_CHANNELS = 2;
45 
46     /**
47      * The SharedPreferences key for whether the stashed handle region is dark.
48      */
49     private static final String SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY =
50             "stashed_handle_region_is_dark";
51 
52     private final TaskbarActivityContext mActivity;
53     private final SharedPreferences mPrefs;
54     private final StashedHandleView mStashedHandleView;
55     private final int mStashedHandleWidth;
56     private final int mStashedHandleHeight;
57     private final RegionSamplingHelper mRegionSamplingHelper;
58     private final MultiValueAlpha mTaskbarStashedHandleAlpha;
59     private final AnimatedFloat mTaskbarStashedHandleHintScale = new AnimatedFloat(
60             this::updateStashedHandleHintScale);
61 
62     // Initialized in init.
63     private TaskbarControllers mControllers;
64 
65     // The bounds we want to clip to in the settled state when showing the stashed handle.
66     private final Rect mStashedHandleBounds = new Rect();
67     private float mStashedHandleRadius;
68 
69     // When the reveal animation is cancelled, we can assume it's about to create a new animation,
70     // which should start off at the same point the cancelled one left off.
71     private float mStartProgressForNextRevealAnim;
72     private boolean mWasLastRevealAnimReversed;
73 
StashedHandleViewController(TaskbarActivityContext activity, StashedHandleView stashedHandleView)74     public StashedHandleViewController(TaskbarActivityContext activity,
75             StashedHandleView stashedHandleView) {
76         mActivity = activity;
77         mPrefs = Utilities.getPrefs(mActivity);
78         mStashedHandleView = stashedHandleView;
79         mTaskbarStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, NUM_ALPHA_CHANNELS);
80         mTaskbarStashedHandleAlpha.setUpdateVisibility(true);
81         mStashedHandleView.updateHandleColor(
82                 mPrefs.getBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY, false),
83                 false /* animate */);
84         final Resources resources = mActivity.getResources();
85         mStashedHandleWidth = resources.getDimensionPixelSize(R.dimen.taskbar_stashed_handle_width);
86         mStashedHandleHeight = resources.getDimensionPixelSize(
87                 R.dimen.taskbar_stashed_handle_height);
88         mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView,
89                 new RegionSamplingHelper.SamplingCallback() {
90                     @Override
91                     public void onRegionDarknessChanged(boolean isRegionDark) {
92                         mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */);
93                         mPrefs.edit().putBoolean(SHARED_PREFS_STASHED_HANDLE_REGION_DARK_KEY,
94                                 isRegionDark).apply();
95                     }
96 
97                     @Override
98                     public Rect getSampledRegion(View sampledView) {
99                         return mStashedHandleView.getSampledRegion();
100                     }
101                 }, Executors.UI_HELPER_EXECUTOR);
102     }
103 
init(TaskbarControllers controllers)104     public void init(TaskbarControllers controllers) {
105         mControllers = controllers;
106         mStashedHandleView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize;
107 
108         mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_STASHED).setValue(0);
109         mTaskbarStashedHandleHintScale.updateValue(1f);
110 
111         final int stashedTaskbarHeight = mControllers.taskbarStashController.getStashedHeight();
112         mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
113             @Override
114             public void getOutline(View view, Outline outline) {
115                 final int stashedCenterX = view.getWidth() / 2;
116                 final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
117                 mStashedHandleBounds.set(
118                         stashedCenterX - mStashedHandleWidth / 2,
119                         stashedCenterY - mStashedHandleHeight / 2,
120                         stashedCenterX + mStashedHandleWidth / 2,
121                         stashedCenterY + mStashedHandleHeight / 2);
122                 mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
123                 mStashedHandleRadius = view.getHeight() / 2f;
124                 outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius);
125             }
126         });
127 
128         mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> {
129             final int stashedCenterX = view.getWidth() / 2;
130             final int stashedCenterY = view.getHeight() - stashedTaskbarHeight / 2;
131 
132             view.setPivotX(stashedCenterX);
133             view.setPivotY(stashedCenterY);
134         });
135     }
136 
onDestroy()137     public void onDestroy() {
138         mRegionSamplingHelper.stopAndDestroy();
139     }
140 
getStashedHandleAlpha()141     public MultiValueAlpha getStashedHandleAlpha() {
142         return mTaskbarStashedHandleAlpha;
143     }
144 
getStashedHandleHintScale()145     public AnimatedFloat getStashedHandleHintScale() {
146         return mTaskbarStashedHandleHintScale;
147     }
148 
149     /**
150      * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle
151      * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape
152      * morphs into the size of where the taskbar icons will be.
153      */
createRevealAnimToIsStashed(boolean isStashed)154     public Animator createRevealAnimToIsStashed(boolean isStashed) {
155         final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
156                 mStashedHandleRadius, mStashedHandleRadius,
157                 mControllers.taskbarViewController.getIconLayoutBounds(), mStashedHandleBounds);
158 
159         boolean isReversed = !isStashed;
160         boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
161         mWasLastRevealAnimReversed = isReversed;
162         if (changingDirection) {
163             mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
164         }
165 
166         ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView,
167                 isReversed, mStartProgressForNextRevealAnim);
168         revealAnim.addListener(new AnimatorListenerAdapter() {
169             @Override
170             public void onAnimationEnd(Animator animation) {
171                 mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction();
172             }
173         });
174         return revealAnim;
175     }
176 
onIsStashed(boolean isStashed)177     public void onIsStashed(boolean isStashed) {
178         mRegionSamplingHelper.setWindowVisible(isStashed);
179         if (isStashed) {
180             mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
181             mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
182         } else {
183             mRegionSamplingHelper.stop();
184         }
185     }
186 
updateStashedHandleHintScale()187     protected void updateStashedHandleHintScale() {
188         mStashedHandleView.setScaleX(mTaskbarStashedHandleHintScale.value);
189         mStashedHandleView.setScaleY(mTaskbarStashedHandleHintScale.value);
190     }
191 
192     /**
193      * Should be called when the home button is disabled, so we can hide this handle as well.
194      */
setIsHomeButtonDisabled(boolean homeDisabled)195     public void setIsHomeButtonDisabled(boolean homeDisabled) {
196         mTaskbarStashedHandleAlpha.getProperty(ALPHA_INDEX_HOME_DISABLED).setValue(
197                 homeDisabled ? 0 : 1);
198     }
199 
isStashedHandleVisible()200     public boolean isStashedHandleVisible() {
201         return mStashedHandleView.getVisibility() == View.VISIBLE;
202     }
203 }
204