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.graphics.Rect; 22 import android.util.Log; 23 import android.view.MotionEvent; 24 import android.view.View; 25 import android.widget.FrameLayout; 26 27 import androidx.annotation.NonNull; 28 29 import com.android.launcher3.R; 30 import com.android.launcher3.anim.AnimatedFloat; 31 import com.android.launcher3.taskbar.TaskbarActivityContext; 32 import com.android.launcher3.taskbar.TaskbarControllers; 33 import com.android.launcher3.taskbar.TaskbarInsetsController; 34 import com.android.launcher3.taskbar.TaskbarStashController; 35 import com.android.launcher3.util.MultiPropertyFactory; 36 import com.android.launcher3.util.MultiValueAlpha; 37 import com.android.quickstep.SystemUiProxy; 38 39 import java.util.List; 40 import java.util.Objects; 41 import java.util.function.Consumer; 42 43 /** 44 * Controller for {@link BubbleBarView}. Manages the visibility of the bubble bar as well as 45 * responding to changes in bubble state provided by BubbleBarController. 46 */ 47 public class BubbleBarViewController { 48 49 private static final String TAG = BubbleBarViewController.class.getSimpleName(); 50 51 private final SystemUiProxy mSystemUiProxy; 52 private final TaskbarActivityContext mActivity; 53 private final BubbleBarView mBarView; 54 private final int mIconSize; 55 56 // Initialized in init. 57 private BubbleStashController mBubbleStashController; 58 private BubbleBarController mBubbleBarController; 59 private BubbleDragController mBubbleDragController; 60 private TaskbarStashController mTaskbarStashController; 61 private TaskbarInsetsController mTaskbarInsetsController; 62 private View.OnClickListener mBubbleClickListener; 63 private View.OnClickListener mBubbleBarClickListener; 64 65 // These are exposed to {@link BubbleStashController} to animate for stashing/un-stashing 66 private final MultiValueAlpha mBubbleBarAlpha; 67 private final AnimatedFloat mBubbleBarScale = new AnimatedFloat(this::updateScale); 68 private final AnimatedFloat mBubbleBarTranslationY = new AnimatedFloat( 69 this::updateTranslationY); 70 71 // Modified when swipe up is happening on the bubble bar or task bar. 72 private float mBubbleBarSwipeUpTranslationY; 73 74 // Whether the bar is hidden for a sysui state. 75 private boolean mHiddenForSysui; 76 // Whether the bar is hidden because there are no bubbles. 77 private boolean mHiddenForNoBubbles; 78 BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView)79 public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) { 80 mActivity = activity; 81 mBarView = barView; 82 mSystemUiProxy = SystemUiProxy.INSTANCE.get(mActivity); 83 mBubbleBarAlpha = new MultiValueAlpha(mBarView, 1 /* num alpha channels */); 84 mBubbleBarAlpha.setUpdateVisibility(true); 85 mIconSize = activity.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size); 86 } 87 init(TaskbarControllers controllers, BubbleControllers bubbleControllers)88 public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) { 89 mBubbleStashController = bubbleControllers.bubbleStashController; 90 mBubbleBarController = bubbleControllers.bubbleBarController; 91 mBubbleDragController = bubbleControllers.bubbleDragController; 92 mTaskbarStashController = controllers.taskbarStashController; 93 mTaskbarInsetsController = controllers.taskbarInsetsController; 94 95 mActivity.addOnDeviceProfileChangeListener(dp -> 96 mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight 97 ); 98 mBarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarHeight; 99 mBubbleBarScale.updateValue(1f); 100 mBubbleClickListener = v -> onBubbleClicked(v); 101 mBubbleBarClickListener = v -> setExpanded(true); 102 mBubbleDragController.setupBubbleBarView(mBarView); 103 mBarView.setOnClickListener(mBubbleBarClickListener); 104 mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) -> 105 mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged() 106 ); 107 } 108 onBubbleClicked(View v)109 private void onBubbleClicked(View v) { 110 BubbleBarItem bubble = ((BubbleView) v).getBubble(); 111 if (bubble == null) { 112 Log.e(TAG, "bubble click listener, bubble was null"); 113 } 114 final String currentlySelected = mBubbleBarController.getSelectedBubbleKey(); 115 if (mBarView.isExpanded() && Objects.equals(bubble.getKey(), currentlySelected)) { 116 // Tapping the currently selected bubble while expanded collapses the view. 117 setExpanded(false); 118 mBubbleStashController.stashBubbleBar(); 119 } else { 120 mBubbleBarController.showAndSelectBubble(bubble); 121 } 122 } 123 124 // 125 // The below animators are exposed to BubbleStashController so it can manage the stashing 126 // animation. 127 // 128 getBubbleBarAlpha()129 public MultiPropertyFactory<View> getBubbleBarAlpha() { 130 return mBubbleBarAlpha; 131 } 132 getBubbleBarScale()133 public AnimatedFloat getBubbleBarScale() { 134 return mBubbleBarScale; 135 } 136 getBubbleBarTranslationY()137 public AnimatedFloat getBubbleBarTranslationY() { 138 return mBubbleBarTranslationY; 139 } 140 141 /** 142 * Whether the bubble bar is visible or not. 143 */ isBubbleBarVisible()144 public boolean isBubbleBarVisible() { 145 return mBarView.getVisibility() == VISIBLE; 146 } 147 148 /** Whether the bubble bar has bubbles. */ hasBubbles()149 public boolean hasBubbles() { 150 return mBubbleBarController.getSelectedBubbleKey() != null; 151 } 152 153 /** 154 * The bounds of the bubble bar. 155 */ getBubbleBarBounds()156 public Rect getBubbleBarBounds() { 157 return mBarView.getBubbleBarBounds(); 158 } 159 160 /** The horizontal margin of the bubble bar from the edge of the screen. */ getHorizontalMargin()161 public int getHorizontalMargin() { 162 return mBarView.getHorizontalMargin(); 163 } 164 165 /** 166 * When the bubble bar is not stashed, it can be collapsed (the icons are in a stack) or 167 * expanded (the icons are in a row). This indicates whether the bubble bar is expanded. 168 */ isExpanded()169 public boolean isExpanded() { 170 return mBarView.isExpanded(); 171 } 172 173 /** 174 * Whether the motion event is within the bounds of the bubble bar. 175 */ isEventOverAnyItem(MotionEvent ev)176 public boolean isEventOverAnyItem(MotionEvent ev) { 177 return mBarView.isEventOverAnyItem(ev); 178 } 179 180 // 181 // Visibility of the bubble bar 182 // 183 184 /** 185 * Returns whether the bubble bar is hidden because there are no bubbles. 186 */ isHiddenForNoBubbles()187 public boolean isHiddenForNoBubbles() { 188 return mHiddenForNoBubbles; 189 } 190 191 /** 192 * Sets whether the bubble bar should be hidden because there are no bubbles. 193 */ setHiddenForBubbles(boolean hidden)194 public void setHiddenForBubbles(boolean hidden) { 195 if (mHiddenForNoBubbles != hidden) { 196 mHiddenForNoBubbles = hidden; 197 updateVisibilityForStateChange(); 198 } 199 } 200 201 /** Sets a callback that updates the selected bubble after the bubble bar collapses. */ setUpdateSelectedBubbleAfterCollapse( Consumer<String> updateSelectedBubbleAfterCollapse)202 public void setUpdateSelectedBubbleAfterCollapse( 203 Consumer<String> updateSelectedBubbleAfterCollapse) { 204 mBarView.setUpdateSelectedBubbleAfterCollapse(updateSelectedBubbleAfterCollapse); 205 } 206 207 /** 208 * Sets whether the bubble bar should be hidden due to SysUI state (e.g. on lockscreen). 209 */ setHiddenForSysui(boolean hidden)210 public void setHiddenForSysui(boolean hidden) { 211 if (mHiddenForSysui != hidden) { 212 mHiddenForSysui = hidden; 213 updateVisibilityForStateChange(); 214 } 215 } 216 217 // TODO: (b/273592694) animate it updateVisibilityForStateChange()218 private void updateVisibilityForStateChange() { 219 if (!mHiddenForSysui && !mHiddenForNoBubbles) { 220 mBarView.setVisibility(VISIBLE); 221 } else { 222 mBarView.setVisibility(INVISIBLE); 223 mBarView.setAlpha(0); 224 mBarView.setExpanded(false); 225 } 226 } 227 228 // 229 // Modifying view related properties. 230 // 231 232 /** 233 * Sets the translation of the bubble bar during the swipe up gesture. 234 */ setTranslationYForSwipe(float transY)235 public void setTranslationYForSwipe(float transY) { 236 mBubbleBarSwipeUpTranslationY = transY; 237 updateTranslationY(); 238 } 239 updateTranslationY()240 private void updateTranslationY() { 241 mBarView.setTranslationY(mBubbleBarTranslationY.value 242 + mBubbleBarSwipeUpTranslationY); 243 } 244 245 /** 246 * Applies scale properties for the entire bubble bar. 247 */ updateScale()248 private void updateScale() { 249 float scale = mBubbleBarScale.value; 250 mBarView.setScaleX(scale); 251 mBarView.setScaleY(scale); 252 } 253 254 // 255 // Manipulating the specific bubble views in the bar 256 // 257 258 /** 259 * Removes the provided bubble from the bubble bar. 260 */ removeBubble(BubbleBarItem b)261 public void removeBubble(BubbleBarItem b) { 262 if (b != null) { 263 mBarView.removeView(b.getView()); 264 } else { 265 Log.w(TAG, "removeBubble, bubble was null!"); 266 } 267 } 268 269 /** 270 * Adds the provided bubble to the bubble bar. 271 */ addBubble(BubbleBarItem b)272 public void addBubble(BubbleBarItem b) { 273 if (b != null) { 274 mBarView.addView(b.getView(), 0, new FrameLayout.LayoutParams(mIconSize, mIconSize)); 275 b.getView().setOnClickListener(mBubbleClickListener); 276 mBubbleDragController.setupBubbleView(b.getView()); 277 } else { 278 Log.w(TAG, "addBubble, bubble was null!"); 279 } 280 } 281 282 /** 283 * Reorders the bubbles based on the provided list. 284 */ reorderBubbles(List<BubbleBarBubble> newOrder)285 public void reorderBubbles(List<BubbleBarBubble> newOrder) { 286 List<BubbleView> viewList = newOrder.stream().filter(Objects::nonNull) 287 .map(BubbleBarBubble::getView).toList(); 288 mBarView.reorder(viewList); 289 } 290 291 /** 292 * Updates the selected bubble. 293 */ updateSelectedBubble(BubbleBarItem newlySelected)294 public void updateSelectedBubble(BubbleBarItem newlySelected) { 295 mBarView.setSelectedBubble(newlySelected.getView()); 296 } 297 298 /** 299 * Sets whether the bubble bar should be expanded (not unstashed, but have the contents 300 * within it expanded). This method notifies SystemUI that the bubble bar is expanded and 301 * showing a selected bubble. This method should ONLY be called from UI events originating 302 * from Launcher. 303 */ setExpanded(boolean isExpanded)304 public void setExpanded(boolean isExpanded) { 305 if (isExpanded != mBarView.isExpanded()) { 306 mBarView.setExpanded(isExpanded); 307 if (!isExpanded) { 308 mSystemUiProxy.collapseBubbles(); 309 } else { 310 mBubbleBarController.showSelectedBubble(); 311 mTaskbarStashController.updateAndAnimateTransientTaskbar(true /* stash */, 312 false /* shouldBubblesFollow */); 313 } 314 } 315 } 316 317 /** 318 * Sets whether the bubble bar should be expanded. This method is used in response to UI events 319 * from SystemUI. 320 */ setExpandedFromSysui(boolean isExpanded)321 public void setExpandedFromSysui(boolean isExpanded) { 322 if (!isExpanded) { 323 mBubbleStashController.stashBubbleBar(); 324 } else { 325 mBubbleStashController.showBubbleBar(true /* expand the bubbles */); 326 } 327 } 328 329 /** 330 * Updates the dragged bubble view in the bubble bar view, and notifies SystemUI 331 * that a bubble is being dragged to dismiss. 332 * @param bubbleView dragged bubble view 333 */ onDragStart(@onNull BubbleView bubbleView)334 public void onDragStart(@NonNull BubbleView bubbleView) { 335 if (bubbleView.getBubble() == null) return; 336 mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ true); 337 mBarView.setDraggedBubble(bubbleView); 338 } 339 340 /** 341 * Notifies SystemUI to expand the selected bubble when the bubble is released. 342 * @param bubbleView dragged bubble view 343 */ onDragRelease(@onNull BubbleView bubbleView)344 public void onDragRelease(@NonNull BubbleView bubbleView) { 345 if (bubbleView.getBubble() == null) return; 346 mSystemUiProxy.onBubbleDrag(bubbleView.getBubble().getKey(), /* isBeingDragged = */ false); 347 } 348 349 /** 350 * Removes the dragged bubble view in the bubble bar view 351 */ onDragEnd()352 public void onDragEnd() { 353 mBarView.setDraggedBubble(null); 354 } 355 356 /** 357 * Called when bubble was dragged into the dismiss target. Notifies System 358 * @param bubble dismissed bubble item 359 */ onDismissBubbleWhileDragging(@onNull BubbleBarItem bubble)360 public void onDismissBubbleWhileDragging(@NonNull BubbleBarItem bubble) { 361 mSystemUiProxy.removeBubble(bubble.getKey()); 362 } 363 364 /** 365 * Called when bubble stack was dragged into the dismiss target 366 */ onDismissAllBubblesWhileDragging()367 public void onDismissAllBubblesWhileDragging() { 368 mSystemUiProxy.removeAllBubbles(); 369 } 370 } 371