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.bubbles; 17 18 import static android.view.View.INVISIBLE; 19 import static android.view.View.VISIBLE; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.animation.ValueAnimator; 24 import android.content.res.Resources; 25 import android.graphics.Outline; 26 import android.graphics.Rect; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.view.ViewOutlineProvider; 30 31 import com.android.launcher3.R; 32 import com.android.launcher3.anim.RevealOutlineAnimation; 33 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; 34 import com.android.launcher3.taskbar.StashedHandleView; 35 import com.android.launcher3.taskbar.TaskbarActivityContext; 36 import com.android.launcher3.taskbar.TaskbarControllers; 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 import com.android.wm.shell.common.bubbles.BubbleBarLocation; 42 import com.android.wm.shell.shared.animation.PhysicsAnimator; 43 44 /** 45 * Handles properties/data collection, then passes the results to our stashed handle View to render. 46 */ 47 public class BubbleStashedHandleViewController { 48 49 private final TaskbarActivityContext mActivity; 50 private final StashedHandleView mStashedHandleView; 51 private final MultiValueAlpha mStashedHandleAlpha; 52 53 // Initialized in init. 54 private BubbleBarViewController mBarViewController; 55 private BubbleStashController mBubbleStashController; 56 private RegionSamplingHelper mRegionSamplingHelper; 57 private int mBarSize; 58 private int mStashedTaskbarHeight; 59 private int mStashedHandleWidth; 60 private int mStashedHandleHeight; 61 62 // The bounds we want to clip to in the settled state when showing the stashed handle. 63 private final Rect mStashedHandleBounds = new Rect(); 64 65 // When the reveal animation is cancelled, we can assume it's about to create a new animation, 66 // which should start off at the same point the cancelled one left off. 67 private float mStartProgressForNextRevealAnim; 68 private boolean mWasLastRevealAnimReversed; 69 70 // XXX: if there are more of these maybe do state flags instead 71 private boolean mHiddenForSysui; 72 private boolean mHiddenForNoBubbles; 73 private boolean mHiddenForHomeButtonDisabled; 74 BubbleStashedHandleViewController(TaskbarActivityContext activity, StashedHandleView stashedHandleView)75 public BubbleStashedHandleViewController(TaskbarActivityContext activity, 76 StashedHandleView stashedHandleView) { 77 mActivity = activity; 78 mStashedHandleView = stashedHandleView; 79 mStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1); 80 } 81 init(TaskbarControllers controllers, BubbleControllers bubbleControllers)82 public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) { 83 mBarViewController = bubbleControllers.bubbleBarViewController; 84 mBubbleStashController = bubbleControllers.bubbleStashController; 85 86 Resources resources = mActivity.getResources(); 87 mStashedHandleHeight = resources.getDimensionPixelSize( 88 R.dimen.bubblebar_stashed_handle_height); 89 mStashedHandleWidth = resources.getDimensionPixelSize( 90 R.dimen.bubblebar_stashed_handle_width); 91 mBarSize = resources.getDimensionPixelSize(R.dimen.bubblebar_size); 92 93 final int bottomMargin = resources.getDimensionPixelSize( 94 R.dimen.transient_taskbar_bottom_margin); 95 mStashedHandleView.getLayoutParams().height = mBarSize + bottomMargin; 96 97 mStashedHandleAlpha.get(0).setValue(0); 98 99 mStashedTaskbarHeight = resources.getDimensionPixelSize( 100 R.dimen.bubblebar_stashed_size); 101 mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() { 102 @Override 103 public void getOutline(View view, Outline outline) { 104 float stashedHandleRadius = view.getHeight() / 2f; 105 outline.setRoundRect(mStashedHandleBounds, stashedHandleRadius); 106 } 107 }); 108 109 mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView, 110 new RegionSamplingHelper.SamplingCallback() { 111 @Override 112 public void onRegionDarknessChanged(boolean isRegionDark) { 113 mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */); 114 } 115 116 @Override 117 public Rect getSampledRegion(View sampledView) { 118 return mStashedHandleView.getSampledRegion(); 119 } 120 }, Executors.UI_HELPER_EXECUTOR); 121 122 mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> 123 updateBounds(mBarViewController.getBubbleBarLocation())); 124 } 125 126 /** Returns the [PhysicsAnimator] for the stashed handle view. */ getPhysicsAnimator()127 public PhysicsAnimator<View> getPhysicsAnimator() { 128 return PhysicsAnimator.getInstance(mStashedHandleView); 129 } 130 updateBounds(BubbleBarLocation bubbleBarLocation)131 private void updateBounds(BubbleBarLocation bubbleBarLocation) { 132 // As more bubbles get added, the icon bounds become larger. To ensure a consistent 133 // handle bar position, we pin it to the edge of the screen. 134 final int stashedCenterY = mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2; 135 if (bubbleBarLocation.isOnLeft(mStashedHandleView.isLayoutRtl())) { 136 final int left = mBarViewController.getHorizontalMargin(); 137 mStashedHandleBounds.set( 138 left, 139 stashedCenterY - mStashedHandleHeight / 2, 140 left + mStashedHandleWidth, 141 stashedCenterY + mStashedHandleHeight / 2); 142 mStashedHandleView.setPivotX(0); 143 } else { 144 final int right = 145 mActivity.getDeviceProfile().widthPx - mBarViewController.getHorizontalMargin(); 146 mStashedHandleBounds.set( 147 right - mStashedHandleWidth, 148 stashedCenterY - mStashedHandleHeight / 2, 149 right, 150 stashedCenterY + mStashedHandleHeight / 2); 151 mStashedHandleView.setPivotX(mStashedHandleView.getWidth()); 152 } 153 154 mStashedHandleView.updateSampledRegion(mStashedHandleBounds); 155 mStashedHandleView.setPivotY(mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2f); 156 } 157 onDestroy()158 public void onDestroy() { 159 mRegionSamplingHelper.stopAndDestroy(); 160 mRegionSamplingHelper = null; 161 } 162 163 /** 164 * Returns the height of the stashed handle. 165 */ getStashedHeight()166 public int getStashedHeight() { 167 return mStashedHandleHeight; 168 } 169 170 /** 171 * Returns the height when the bubble bar is unstashed (so the height of the bubble bar). 172 */ getUnstashedHeight()173 public int getUnstashedHeight() { 174 return mBarSize; 175 } 176 177 /** 178 * Called when system ui state changes. Bubbles don't show when the device is locked. 179 */ setHiddenForSysui(boolean hidden)180 public void setHiddenForSysui(boolean hidden) { 181 if (mHiddenForSysui != hidden) { 182 mHiddenForSysui = hidden; 183 updateVisibilityForStateChange(); 184 } 185 } 186 187 /** 188 * Called when the handle should be hidden (or shown) because there are no bubbles 189 * (or 1+ bubbles). 190 */ setHiddenForBubbles(boolean hidden)191 public void setHiddenForBubbles(boolean hidden) { 192 if (mHiddenForNoBubbles != hidden) { 193 mHiddenForNoBubbles = hidden; 194 updateVisibilityForStateChange(); 195 } 196 } 197 198 /** 199 * Called when the home button is enabled / disabled. Bubbles don't show if home is disabled. 200 */ 201 // TODO: is this needed for bubbles? setIsHomeButtonDisabled(boolean homeDisabled)202 public void setIsHomeButtonDisabled(boolean homeDisabled) { 203 mHiddenForHomeButtonDisabled = homeDisabled; 204 updateVisibilityForStateChange(); 205 } 206 207 // TODO: (b/273592694) animate it? updateVisibilityForStateChange()208 private void updateVisibilityForStateChange() { 209 if (!mHiddenForSysui && !mHiddenForHomeButtonDisabled && !mHiddenForNoBubbles) { 210 mStashedHandleView.setVisibility(VISIBLE); 211 } else { 212 mStashedHandleView.setVisibility(INVISIBLE); 213 mStashedHandleView.setAlpha(0); 214 } 215 updateRegionSampling(); 216 } 217 218 /** 219 * Called when bubble bar is stash state changes so that updates to the stashed handle color 220 * can be started or stopped. 221 */ onIsStashedChanged()222 public void onIsStashedChanged() { 223 updateRegionSampling(); 224 } 225 updateRegionSampling()226 private void updateRegionSampling() { 227 boolean handleVisible = mStashedHandleView.getVisibility() == VISIBLE 228 && mBubbleStashController.isStashed(); 229 if (mRegionSamplingHelper != null) { 230 mRegionSamplingHelper.setWindowVisible(handleVisible); 231 if (handleVisible) { 232 mStashedHandleView.updateSampledRegion(mStashedHandleBounds); 233 mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion()); 234 } else { 235 mRegionSamplingHelper.stop(); 236 } 237 } 238 } 239 240 /** 241 * Sets the translation of the stashed handle during the swipe up gesture. 242 */ setTranslationYForSwipe(float transY)243 public void setTranslationYForSwipe(float transY) { 244 mStashedHandleView.setTranslationY(transY); 245 } 246 247 /** 248 * Used by {@link BubbleStashController} to animate the handle when stashing or un stashing. 249 */ getStashedHandleAlpha()250 public MultiPropertyFactory<View> getStashedHandleAlpha() { 251 return mStashedHandleAlpha; 252 } 253 254 /** 255 * Creates and returns an Animator that updates the stashed handle shape and size. 256 * When stashed, the shape is a thin rounded pill. When unstashed, the shape morphs into 257 * the size of where the bubble bar icons will be. 258 */ createRevealAnimToIsStashed(boolean isStashed)259 public Animator createRevealAnimToIsStashed(boolean isStashed) { 260 Rect bubbleBarBounds = new Rect(mBarViewController.getBubbleBarBounds()); 261 262 // Account for the full visual height of the bubble bar 263 int heightDiff = (mBarSize - bubbleBarBounds.height()) / 2; 264 bubbleBarBounds.top -= heightDiff; 265 bubbleBarBounds.bottom += heightDiff; 266 float stashedHandleRadius = mStashedHandleView.getHeight() / 2f; 267 final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider( 268 stashedHandleRadius, stashedHandleRadius, bubbleBarBounds, mStashedHandleBounds); 269 270 boolean isReversed = !isStashed; 271 boolean changingDirection = mWasLastRevealAnimReversed != isReversed; 272 mWasLastRevealAnimReversed = isReversed; 273 if (changingDirection) { 274 mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim; 275 } 276 277 ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView, 278 isReversed, mStartProgressForNextRevealAnim); 279 revealAnim.addListener(new AnimatorListenerAdapter() { 280 @Override 281 public void onAnimationEnd(Animator animation) { 282 mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction(); 283 } 284 }); 285 return revealAnim; 286 } 287 288 /** Checks that the stash handle is visible and that the motion event is within bounds. */ isEventOverHandle(MotionEvent ev)289 public boolean isEventOverHandle(MotionEvent ev) { 290 if (mStashedHandleView.getVisibility() != VISIBLE) { 291 return false; 292 } 293 294 // the bounds of the handle only include the visible part, so we check that the Y coordinate 295 // is anywhere within the stashed taskbar height. 296 int top = mActivity.getDeviceProfile().heightPx - mStashedTaskbarHeight; 297 298 return (int) ev.getRawY() >= top && containsX((int) ev.getRawX()); 299 } 300 301 /** Checks if the given x coordinate is within the stashed handle bounds. */ containsX(int x)302 public boolean containsX(int x) { 303 return x >= mStashedHandleBounds.left && x <= mStashedHandleBounds.right; 304 } 305 306 /** Set a bubble bar location */ setBubbleBarLocation(BubbleBarLocation bubbleBarLocation)307 public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { 308 updateBounds(bubbleBarLocation); 309 } 310 } 311