1 /* 2 * Copyright (C) 2022 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 17 package com.android.systemui.shade; 18 19 import android.content.ComponentCallbacks2; 20 import android.os.Looper; 21 import android.util.Log; 22 import android.view.MotionEvent; 23 import android.view.ViewTreeObserver; 24 import android.view.WindowManager; 25 import android.view.WindowManagerGlobal; 26 27 import com.android.systemui.assist.AssistManager; 28 import com.android.systemui.dagger.SysUISingleton; 29 import com.android.systemui.dagger.qualifiers.Main; 30 import com.android.systemui.log.LogBuffer; 31 import com.android.systemui.log.dagger.ShadeTouchLog; 32 import com.android.systemui.plugins.statusbar.StatusBarStateController; 33 import com.android.systemui.statusbar.CommandQueue; 34 import com.android.systemui.statusbar.NotificationPresenter; 35 import com.android.systemui.statusbar.NotificationShadeWindowController; 36 import com.android.systemui.statusbar.StatusBarState; 37 import com.android.systemui.statusbar.notification.row.NotificationGutsManager; 38 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 39 import com.android.systemui.statusbar.policy.DeviceProvisionedController; 40 import com.android.systemui.statusbar.policy.KeyguardStateController; 41 import com.android.systemui.statusbar.window.StatusBarWindowController; 42 43 import dagger.Lazy; 44 45 import java.util.ArrayList; 46 import java.util.concurrent.Executor; 47 48 import javax.inject.Inject; 49 50 /** An implementation of {@link ShadeController}. */ 51 @SysUISingleton 52 public final class ShadeControllerImpl implements ShadeController { 53 54 private static final String TAG = "ShadeControllerImpl"; 55 private static final boolean SPEW = false; 56 57 private final int mDisplayId; 58 59 private final CommandQueue mCommandQueue; 60 private final Executor mMainExecutor; 61 private final LogBuffer mTouchLog; 62 private final KeyguardStateController mKeyguardStateController; 63 private final NotificationShadeWindowController mNotificationShadeWindowController; 64 private final StatusBarStateController mStatusBarStateController; 65 private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 66 private final StatusBarWindowController mStatusBarWindowController; 67 private final DeviceProvisionedController mDeviceProvisionedController; 68 69 private final Lazy<ShadeViewController> mShadeViewControllerLazy; 70 private final Lazy<AssistManager> mAssistManagerLazy; 71 private final Lazy<NotificationGutsManager> mGutsManager; 72 73 private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>(); 74 75 private boolean mExpandedVisible; 76 77 private NotificationPresenter mPresenter; 78 private NotificationShadeWindowViewController mNotificationShadeWindowViewController; 79 private ShadeVisibilityListener mShadeVisibilityListener; 80 81 @Inject ShadeControllerImpl( CommandQueue commandQueue, @Main Executor mainExecutor, @ShadeTouchLog LogBuffer touchLog, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, StatusBarWindowController statusBarWindowController, DeviceProvisionedController deviceProvisionedController, NotificationShadeWindowController notificationShadeWindowController, WindowManager windowManager, Lazy<ShadeViewController> shadeViewControllerLazy, Lazy<AssistManager> assistManagerLazy, Lazy<NotificationGutsManager> gutsManager )82 public ShadeControllerImpl( 83 CommandQueue commandQueue, 84 @Main Executor mainExecutor, 85 @ShadeTouchLog LogBuffer touchLog, 86 KeyguardStateController keyguardStateController, 87 StatusBarStateController statusBarStateController, 88 StatusBarKeyguardViewManager statusBarKeyguardViewManager, 89 StatusBarWindowController statusBarWindowController, 90 DeviceProvisionedController deviceProvisionedController, 91 NotificationShadeWindowController notificationShadeWindowController, 92 WindowManager windowManager, 93 Lazy<ShadeViewController> shadeViewControllerLazy, 94 Lazy<AssistManager> assistManagerLazy, 95 Lazy<NotificationGutsManager> gutsManager 96 ) { 97 mCommandQueue = commandQueue; 98 mMainExecutor = mainExecutor; 99 mTouchLog = touchLog; 100 mShadeViewControllerLazy = shadeViewControllerLazy; 101 mStatusBarStateController = statusBarStateController; 102 mStatusBarWindowController = statusBarWindowController; 103 mDeviceProvisionedController = deviceProvisionedController; 104 mGutsManager = gutsManager; 105 mNotificationShadeWindowController = notificationShadeWindowController; 106 mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; 107 mDisplayId = windowManager.getDefaultDisplay().getDisplayId(); 108 mKeyguardStateController = keyguardStateController; 109 mAssistManagerLazy = assistManagerLazy; 110 } 111 112 @Override instantExpandShade()113 public void instantExpandShade() { 114 // Make our window larger and the panel expanded. 115 makeExpandedVisible(true /* force */); 116 getShadeViewController().expand(false /* animate */); 117 mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */); 118 } 119 120 @Override animateCollapseShade(int flags, boolean force, boolean delayed, float speedUpFactor)121 public void animateCollapseShade(int flags, boolean force, boolean delayed, 122 float speedUpFactor) { 123 if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) { 124 runPostCollapseRunnables(); 125 return; 126 } 127 if (SPEW) { 128 Log.d(TAG, 129 "animateCollapse(): mExpandedVisible=" + mExpandedVisible + "flags=" + flags); 130 } 131 if (getNotificationShadeWindowView() != null 132 && getShadeViewController().canBeCollapsed() 133 && (flags & CommandQueue.FLAG_EXCLUDE_NOTIFICATION_PANEL) == 0) { 134 // release focus immediately to kick off focus change transition 135 mNotificationShadeWindowController.setNotificationShadeFocusable(false); 136 137 mNotificationShadeWindowViewController.cancelExpandHelper(); 138 getShadeViewController().collapse(true, delayed, speedUpFactor); 139 } 140 } 141 142 @Override animateExpandShade()143 public void animateExpandShade() { 144 if (!mCommandQueue.panelsEnabled()) { 145 return; 146 } 147 getShadeViewController().expandToNotifications(); 148 } 149 150 @Override animateExpandQs()151 public void animateExpandQs() { 152 if (!mCommandQueue.panelsEnabled()) { 153 return; 154 } 155 // Settings are not available in setup 156 if (!mDeviceProvisionedController.isCurrentUserSetup()) return; 157 158 getShadeViewController().expandToQs(); 159 } 160 161 @Override closeShadeIfOpen()162 public boolean closeShadeIfOpen() { 163 if (!getShadeViewController().isFullyCollapsed()) { 164 mCommandQueue.animateCollapsePanels( 165 CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */); 166 notifyVisibilityChanged(false); 167 mAssistManagerLazy.get().hideAssist(); 168 } 169 return false; 170 } 171 172 @Override isKeyguard()173 public boolean isKeyguard() { 174 return mStatusBarStateController.getState() == StatusBarState.KEYGUARD; 175 } 176 177 @Override isShadeFullyOpen()178 public boolean isShadeFullyOpen() { 179 return getShadeViewController().isShadeFullyExpanded(); 180 } 181 182 @Override isExpandingOrCollapsing()183 public boolean isExpandingOrCollapsing() { 184 return getShadeViewController().isExpandingOrCollapsing(); 185 } 186 @Override postAnimateCollapseShade()187 public void postAnimateCollapseShade() { 188 mMainExecutor.execute(this::animateCollapseShade); 189 } 190 191 @Override postAnimateForceCollapseShade()192 public void postAnimateForceCollapseShade() { 193 mMainExecutor.execute(this::animateCollapseShadeForced); 194 } 195 196 @Override postAnimateExpandQs()197 public void postAnimateExpandQs() { 198 mMainExecutor.execute(this::animateExpandQs); 199 } 200 201 @Override postOnShadeExpanded(Runnable executable)202 public void postOnShadeExpanded(Runnable executable) { 203 getShadeViewController().addOnGlobalLayoutListener( 204 new ViewTreeObserver.OnGlobalLayoutListener() { 205 @Override 206 public void onGlobalLayout() { 207 if (getNotificationShadeWindowView().isVisibleToUser()) { 208 getShadeViewController().removeOnGlobalLayoutListener(this); 209 getShadeViewController().postToView(executable); 210 } 211 } 212 }); 213 } 214 215 @Override addPostCollapseAction(Runnable action)216 public void addPostCollapseAction(Runnable action) { 217 mPostCollapseRunnables.add(action); 218 } 219 220 @Override runPostCollapseRunnables()221 public void runPostCollapseRunnables() { 222 ArrayList<Runnable> clonedList = new ArrayList<>(mPostCollapseRunnables); 223 mPostCollapseRunnables.clear(); 224 int size = clonedList.size(); 225 for (int i = 0; i < size; i++) { 226 clonedList.get(i).run(); 227 } 228 mStatusBarKeyguardViewManager.readyForKeyguardDone(); 229 } 230 231 @Override collapseShade()232 public boolean collapseShade() { 233 if (!getShadeViewController().isFullyCollapsed()) { 234 // close the shade if it was open 235 animateCollapseShadeForcedDelayed(); 236 notifyVisibilityChanged(false); 237 238 return true; 239 } else { 240 return false; 241 } 242 } 243 244 @Override collapseShade(boolean animate)245 public void collapseShade(boolean animate) { 246 if (animate) { 247 boolean willCollapse = collapseShade(); 248 if (!willCollapse) { 249 runPostCollapseRunnables(); 250 } 251 } else if (!mPresenter.isPresenterFullyCollapsed()) { 252 instantCollapseShade(); 253 notifyVisibilityChanged(false); 254 } else { 255 runPostCollapseRunnables(); 256 } 257 } 258 259 @Override cancelExpansionAndCollapseShade()260 public void cancelExpansionAndCollapseShade() { 261 if (getShadeViewController().isTracking()) { 262 mNotificationShadeWindowViewController.cancelCurrentTouch(); 263 } 264 if (getShadeViewController().isPanelExpanded() 265 && mStatusBarStateController.getState() == StatusBarState.SHADE) { 266 animateCollapseShade(); 267 } 268 } 269 270 @Override collapseOnMainThread()271 public void collapseOnMainThread() { 272 if (Looper.getMainLooper().isCurrentThread()) { 273 collapseShade(); 274 } else { 275 mMainExecutor.execute(this::collapseShade); 276 } 277 } 278 279 @Override onStatusBarTouch(MotionEvent event)280 public void onStatusBarTouch(MotionEvent event) { 281 if (event.getAction() == MotionEvent.ACTION_UP) { 282 if (mExpandedVisible) { 283 animateCollapseShade(); 284 } 285 } 286 } 287 onClosingFinished()288 private void onClosingFinished() { 289 runPostCollapseRunnables(); 290 if (!mPresenter.isPresenterFullyCollapsed()) { 291 // if we set it not to be focusable when collapsing, we have to undo it when we aborted 292 // the closing 293 mNotificationShadeWindowController.setNotificationShadeFocusable(true); 294 } 295 } 296 297 @Override onLaunchAnimationCancelled(boolean isLaunchForActivity)298 public void onLaunchAnimationCancelled(boolean isLaunchForActivity) { 299 if (mPresenter.isPresenterFullyCollapsed() 300 && !mPresenter.isCollapsing() 301 && isLaunchForActivity) { 302 onClosingFinished(); 303 } else { 304 collapseShade(true /* animate */); 305 } 306 } 307 308 @Override onLaunchAnimationEnd(boolean launchIsFullScreen)309 public void onLaunchAnimationEnd(boolean launchIsFullScreen) { 310 if (!mPresenter.isCollapsing()) { 311 onClosingFinished(); 312 } 313 if (launchIsFullScreen) { 314 instantCollapseShade(); 315 } 316 } 317 318 @Override instantCollapseShade()319 public void instantCollapseShade() { 320 getShadeViewController().instantCollapse(); 321 runPostCollapseRunnables(); 322 } 323 324 @Override makeExpandedVisible(boolean force)325 public void makeExpandedVisible(boolean force) { 326 if (SPEW) Log.d(TAG, "Make expanded visible: expanded visible=" + mExpandedVisible); 327 if (!force && (mExpandedVisible || !mCommandQueue.panelsEnabled())) { 328 return; 329 } 330 331 mExpandedVisible = true; 332 333 // Expand the window to encompass the full screen in anticipation of the drag. 334 // It's only possible to do atomically because the status bar is at the top of the screen! 335 mNotificationShadeWindowController.setPanelVisible(true); 336 337 notifyVisibilityChanged(true); 338 mCommandQueue.recomputeDisableFlags(mDisplayId, !force /* animate */); 339 notifyExpandedVisibleChanged(true); 340 } 341 342 @Override makeExpandedInvisible()343 public void makeExpandedInvisible() { 344 if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible); 345 346 if (!mExpandedVisible || getNotificationShadeWindowView() == null) { 347 return; 348 } 349 350 // Ensure the panel is fully collapsed (just in case; bug 6765842, 7260868) 351 getShadeViewController().collapse(false, false, 1.0f); 352 353 mExpandedVisible = false; 354 notifyVisibilityChanged(false); 355 356 // Update the visibility of notification shade and status bar window. 357 mNotificationShadeWindowController.setPanelVisible(false); 358 mStatusBarWindowController.setForceStatusBarVisible(false); 359 360 // Close any guts that might be visible 361 mGutsManager.get().closeAndSaveGuts( 362 true /* removeLeavebehind */, 363 true /* force */, 364 true /* removeControls */, 365 -1 /* x */, 366 -1 /* y */, 367 true /* resetMenu */); 368 369 runPostCollapseRunnables(); 370 notifyExpandedVisibleChanged(false); 371 mCommandQueue.recomputeDisableFlags( 372 mDisplayId, 373 getShadeViewController().shouldHideStatusBarIconsWhenExpanded()); 374 375 // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in 376 // the bouncer appear animation. 377 if (!mKeyguardStateController.isShowing()) { 378 WindowManagerGlobal.getInstance().trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); 379 } 380 } 381 382 @Override isExpandedVisible()383 public boolean isExpandedVisible() { 384 return mExpandedVisible; 385 } 386 387 @Override setVisibilityListener(ShadeVisibilityListener listener)388 public void setVisibilityListener(ShadeVisibilityListener listener) { 389 mShadeVisibilityListener = listener; 390 } 391 notifyVisibilityChanged(boolean visible)392 private void notifyVisibilityChanged(boolean visible) { 393 mShadeVisibilityListener.visibilityChanged(visible); 394 } 395 notifyExpandedVisibleChanged(boolean expandedVisible)396 private void notifyExpandedVisibleChanged(boolean expandedVisible) { 397 mShadeVisibilityListener.expandedVisibleChanged(expandedVisible); 398 } 399 400 @Override setNotificationPresenter(NotificationPresenter presenter)401 public void setNotificationPresenter(NotificationPresenter presenter) { 402 mPresenter = presenter; 403 } 404 405 @Override setNotificationShadeWindowViewController( NotificationShadeWindowViewController controller)406 public void setNotificationShadeWindowViewController( 407 NotificationShadeWindowViewController controller) { 408 mNotificationShadeWindowViewController = controller; 409 } 410 getNotificationShadeWindowView()411 private NotificationShadeWindowView getNotificationShadeWindowView() { 412 return mNotificationShadeWindowViewController.getView(); 413 } 414 getShadeViewController()415 private ShadeViewController getShadeViewController() { 416 return mShadeViewControllerLazy.get(); 417 } 418 419 @Override start()420 public void start() { 421 TouchLogger.logTouchesTo(mTouchLog); 422 getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables); 423 getShadeViewController().setOpenCloseListener( 424 new OpenCloseListener() { 425 @Override 426 public void onClosingFinished() { 427 ShadeControllerImpl.this.onClosingFinished(); 428 } 429 430 @Override 431 public void onOpenStarted() { 432 makeExpandedVisible(false); 433 } 434 }); 435 } 436 } 437