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.annotation.Nullable; 25 import android.content.res.Resources; 26 import android.graphics.Outline; 27 import android.graphics.Rect; 28 import android.view.MotionEvent; 29 import android.view.View; 30 import android.view.ViewOutlineProvider; 31 32 import com.android.launcher3.DeviceProfile; 33 import com.android.launcher3.R; 34 import com.android.launcher3.anim.RevealOutlineAnimation; 35 import com.android.launcher3.anim.RoundedRectRevealOutlineProvider; 36 import com.android.launcher3.taskbar.StashedHandleView; 37 import com.android.launcher3.taskbar.TaskbarActivityContext; 38 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController; 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.wm.shell.shared.animation.PhysicsAnimator; 43 import com.android.wm.shell.shared.bubbles.BubbleBarLocation; 44 import com.android.wm.shell.shared.handles.RegionSamplingHelper; 45 46 /** 47 * Handles properties/data collection, then passes the results to our stashed handle View to render. 48 */ 49 public class BubbleStashedHandleViewController { 50 51 private final TaskbarActivityContext mActivity; 52 private final StashedHandleView mStashedHandleView; 53 private final MultiValueAlpha mStashedHandleAlpha; 54 private float mTranslationForSwipeY; 55 private float mTranslationForStashY; 56 57 // Initialized in init. 58 private BubbleBarViewController mBarViewController; 59 private BubbleStashController mBubbleStashController; 60 private RegionSamplingHelper mRegionSamplingHelper; 61 // Height of the area for the stash handle. Handle will be drawn in the center of this. 62 // This is also the area where touch is handled on the handle. 63 private int mStashedBubbleBarHeight; 64 private int mStashedHandleWidth; 65 private int mStashedHandleHeight; 66 67 // The bounds of the stashed handle in settled state. 68 private final Rect mStashedHandleBounds = new Rect(); 69 private float mStashedHandleRadius; 70 71 // When the reveal animation is cancelled, we can assume it's about to create a new animation, 72 // which should start off at the same point the cancelled one left off. 73 private float mStartProgressForNextRevealAnim; 74 // Use a nullable boolean to handle initial case where the last animation direction is not known 75 @Nullable 76 private Boolean mWasLastRevealAnimReversed = null; 77 78 // XXX: if there are more of these maybe do state flags instead 79 private boolean mHiddenForSysui; 80 private boolean mHiddenForNoBubbles; 81 private boolean mHiddenForHomeButtonDisabled; 82 BubbleStashedHandleViewController(TaskbarActivityContext activity, StashedHandleView stashedHandleView)83 public BubbleStashedHandleViewController(TaskbarActivityContext activity, 84 StashedHandleView stashedHandleView) { 85 mActivity = activity; 86 mStashedHandleView = stashedHandleView; 87 mStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1); 88 mStashedHandleAlpha.setUpdateVisibility(true); 89 } 90 91 /** Initialize controller. */ init(BubbleControllers bubbleControllers)92 public void init(BubbleControllers bubbleControllers) { 93 mBarViewController = bubbleControllers.bubbleBarViewController; 94 mBubbleStashController = bubbleControllers.bubbleStashController; 95 96 DeviceProfile deviceProfile = mActivity.getDeviceProfile(); 97 Resources resources = mActivity.getResources(); 98 mStashedHandleHeight = resources.getDimensionPixelSize( 99 R.dimen.bubblebar_stashed_handle_height); 100 mStashedHandleWidth = resources.getDimensionPixelSize( 101 R.dimen.bubblebar_stashed_handle_width); 102 103 int barSize = resources.getDimensionPixelSize(R.dimen.bubblebar_size); 104 // Use the max translation for bubble bar whether it is on the home screen or in app. 105 // Use values directly from device profile to avoid referencing other bubble controllers 106 // during init flow. 107 int maxTy = Math.max(deviceProfile.hotseatBarBottomSpacePx, 108 deviceProfile.taskbarBottomMargin); 109 // Adjust handle view size to accommodate the handle morphing into the bubble bar 110 mStashedHandleView.getLayoutParams().height = barSize + maxTy; 111 112 mStashedHandleAlpha.get(0).setValue(0); 113 114 mStashedBubbleBarHeight = resources.getDimensionPixelSize( 115 R.dimen.bubblebar_stashed_size); 116 mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() { 117 @Override 118 public void getOutline(View view, Outline outline) { 119 mStashedHandleRadius = view.getHeight() / 2f; 120 outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius); 121 } 122 }); 123 124 mRegionSamplingHelper = new RegionSamplingHelper(mStashedHandleView, 125 new RegionSamplingHelper.SamplingCallback() { 126 @Override 127 public void onRegionDarknessChanged(boolean isRegionDark) { 128 mStashedHandleView.updateHandleColor(isRegionDark, true /* animate */); 129 } 130 131 @Override 132 public Rect getSampledRegion(View sampledView) { 133 return mStashedHandleView.getSampledRegion(); 134 } 135 }, Executors.MAIN_EXECUTOR, Executors.UI_HELPER_EXECUTOR); 136 137 mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> 138 updateBounds(mBarViewController.getBubbleBarLocation())); 139 } 140 141 /** Returns the [PhysicsAnimator] for the stashed handle view. */ getPhysicsAnimator()142 public PhysicsAnimator<View> getPhysicsAnimator() { 143 return PhysicsAnimator.getInstance(mStashedHandleView); 144 } 145 updateBounds(BubbleBarLocation bubbleBarLocation)146 private void updateBounds(BubbleBarLocation bubbleBarLocation) { 147 // As more bubbles get added, the icon bounds become larger. To ensure a consistent 148 // handle bar position, we pin it to the edge of the screen. 149 final int stashedCenterY = mStashedHandleView.getHeight() - mStashedBubbleBarHeight / 2; 150 final int stashedCenterX; 151 if (bubbleBarLocation.isOnLeft(mStashedHandleView.isLayoutRtl())) { 152 final int left = mBarViewController.getHorizontalMargin(); 153 stashedCenterX = left + mStashedHandleWidth / 2; 154 } else { 155 final int right = 156 mStashedHandleView.getRight() - mBarViewController.getHorizontalMargin(); 157 stashedCenterX = right - mStashedHandleWidth / 2; 158 } 159 mStashedHandleBounds.set( 160 stashedCenterX - mStashedHandleWidth / 2, 161 stashedCenterY - mStashedHandleHeight / 2, 162 stashedCenterX + mStashedHandleWidth / 2, 163 stashedCenterY + mStashedHandleHeight / 2 164 ); 165 mStashedHandleView.updateSampledRegion(mStashedHandleBounds); 166 mStashedHandleView.setPivotX(stashedCenterX); 167 mStashedHandleView.setPivotY(stashedCenterY); 168 } 169 onDestroy()170 public void onDestroy() { 171 mRegionSamplingHelper.stopAndDestroy(); 172 mRegionSamplingHelper = null; 173 } 174 175 /** 176 * Returns the width of the stashed handle. 177 */ getStashedWidth()178 public int getStashedWidth() { 179 return mStashedHandleWidth; 180 } 181 182 /** 183 * Returns the height of the stashed handle. 184 */ getStashedHeight()185 public int getStashedHeight() { 186 return mStashedHandleHeight; 187 } 188 189 /** 190 * Returns bounds of the stashed handle view 191 */ getBounds(Rect bounds)192 public void getBounds(Rect bounds) { 193 bounds.set(mStashedHandleBounds); 194 } 195 196 /** 197 * Called when system ui state changes. Bubbles don't show when the device is locked. 198 */ setHiddenForSysui(boolean hidden)199 public void setHiddenForSysui(boolean hidden) { 200 if (mHiddenForSysui != hidden) { 201 mHiddenForSysui = hidden; 202 updateVisibilityForStateChange(); 203 } 204 } 205 206 /** 207 * Called when the handle should be hidden (or shown) because there are no bubbles 208 * (or 1+ bubbles). 209 */ setHiddenForBubbles(boolean hidden)210 public void setHiddenForBubbles(boolean hidden) { 211 if (mHiddenForNoBubbles != hidden) { 212 mHiddenForNoBubbles = hidden; 213 updateVisibilityForStateChange(); 214 } 215 } 216 217 /** 218 * Called when the home button is enabled / disabled. Bubbles don't show if home is disabled. 219 */ 220 // TODO: is this needed for bubbles? setIsHomeButtonDisabled(boolean homeDisabled)221 public void setIsHomeButtonDisabled(boolean homeDisabled) { 222 mHiddenForHomeButtonDisabled = homeDisabled; 223 updateVisibilityForStateChange(); 224 } 225 226 // TODO: (b/273592694) animate it? updateVisibilityForStateChange()227 private void updateVisibilityForStateChange() { 228 if (!mHiddenForSysui && !mHiddenForHomeButtonDisabled && !mHiddenForNoBubbles) { 229 mStashedHandleView.setVisibility(VISIBLE); 230 } else { 231 mStashedHandleView.setVisibility(INVISIBLE); 232 mStashedHandleView.setAlpha(0); 233 } 234 updateRegionSampling(); 235 } 236 237 /** 238 * Called when bubble bar is stash state changes so that updates to the stashed handle color 239 * can be started or stopped. 240 */ onIsStashedChanged()241 public void onIsStashedChanged() { 242 updateRegionSampling(); 243 } 244 updateRegionSampling()245 private void updateRegionSampling() { 246 boolean handleVisible = mStashedHandleView.getVisibility() == VISIBLE 247 && mBubbleStashController.isStashed(); 248 if (mRegionSamplingHelper != null) { 249 mRegionSamplingHelper.setWindowVisible(handleVisible); 250 if (handleVisible) { 251 mStashedHandleView.updateSampledRegion(mStashedHandleBounds); 252 mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion()); 253 } else { 254 mRegionSamplingHelper.stop(); 255 } 256 } 257 } 258 259 /** 260 * Sets the translation of the stashed handle during the swipe up gesture. 261 */ setTranslationYForSwipe(float transY)262 public void setTranslationYForSwipe(float transY) { 263 mTranslationForSwipeY = transY; 264 updateTranslationY(); 265 } 266 267 /** 268 * Sets the translation of the stashed handle during the spring on stash animation. 269 */ setTranslationYForStash(float transY)270 public void setTranslationYForStash(float transY) { 271 mTranslationForStashY = transY; 272 updateTranslationY(); 273 } 274 275 /** Sets translation X for stash handle. */ setTranslationX(float translationX)276 public void setTranslationX(float translationX) { 277 mStashedHandleView.setTranslationX(translationX); 278 } 279 updateTranslationY()280 private void updateTranslationY() { 281 mStashedHandleView.setTranslationY(mTranslationForSwipeY + mTranslationForStashY); 282 } 283 284 /** Returns the translation of the stashed handle. */ getTranslationY()285 public float getTranslationY() { 286 return mStashedHandleView.getTranslationY(); 287 } 288 289 /** 290 * Used by {@link BubbleStashController} to animate the handle when stashing or un stashing. 291 */ getStashedHandleAlpha()292 public MultiPropertyFactory<View> getStashedHandleAlpha() { 293 return mStashedHandleAlpha; 294 } 295 296 /** 297 * Creates and returns an Animator that updates the stashed handle shape and size. 298 * When stashed, the shape is a thin rounded pill. When unstashed, the shape morphs into 299 * the size of where the bubble bar icons will be. 300 */ createRevealAnimToIsStashed(boolean isStashed)301 public Animator createRevealAnimToIsStashed(boolean isStashed) { 302 Rect bubbleBarBounds = getLocalBubbleBarBounds(); 303 304 float bubbleBarRadius = bubbleBarBounds.height() / 2f; 305 final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider( 306 bubbleBarRadius, mStashedHandleRadius, bubbleBarBounds, mStashedHandleBounds); 307 308 boolean isReversed = !isStashed; 309 // We are only changing direction when mWasLastRevealAnimReversed is set at least once 310 boolean changingDirection = 311 mWasLastRevealAnimReversed != null && mWasLastRevealAnimReversed != isReversed; 312 313 mWasLastRevealAnimReversed = isReversed; 314 if (changingDirection) { 315 mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim; 316 } 317 318 ValueAnimator revealAnim = handleRevealProvider.createRevealAnimator(mStashedHandleView, 319 isReversed, mStartProgressForNextRevealAnim); 320 revealAnim.addListener(new AnimatorListenerAdapter() { 321 @Override 322 public void onAnimationEnd(Animator animation) { 323 mStartProgressForNextRevealAnim = ((ValueAnimator) animation).getAnimatedFraction(); 324 } 325 }); 326 return revealAnim; 327 } 328 329 /** 330 * Get bounds for the bubble bar in the space of the handle view 331 */ getLocalBubbleBarBounds()332 private Rect getLocalBubbleBarBounds() { 333 // Position the bubble bar bounds to the space of handle view 334 Rect bubbleBarBounds = new Rect(mBarViewController.getBubbleBarBounds()); 335 // Start by moving bubble bar bounds to the bottom of handle view 336 int height = bubbleBarBounds.height(); 337 bubbleBarBounds.bottom = mStashedHandleView.getHeight(); 338 bubbleBarBounds.top = bubbleBarBounds.bottom - height; 339 // Then apply translation that is applied to the bubble bar 340 bubbleBarBounds.offset(0, (int) mBubbleStashController.getBubbleBarTranslationY()); 341 return bubbleBarBounds; 342 } 343 344 /** Checks that the stash handle is visible and that the motion event is within bounds. */ isEventOverHandle(MotionEvent ev)345 public boolean isEventOverHandle(MotionEvent ev) { 346 if (mStashedHandleView.getVisibility() != VISIBLE) { 347 return false; 348 } 349 350 // the bounds of the handle only include the visible part, so we check that the Y coordinate 351 // is anywhere within the stashed height of bubble bar (same as taskbar stashed height). 352 final int top = mActivity.getDeviceProfile().heightPx - mStashedBubbleBarHeight; 353 final float x = ev.getRawX(); 354 return ev.getRawY() >= top && x >= mStashedHandleBounds.left 355 && x <= mStashedHandleBounds.right; 356 } 357 358 /** Set a bubble bar location */ setBubbleBarLocation(BubbleBarLocation bubbleBarLocation)359 public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { 360 updateBounds(bubbleBarLocation); 361 } 362 } 363