1 /* 2 * Copyright (C) 2012 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.statusbar.phone; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.ValueAnimator; 23 import android.app.ActivityManager; 24 import android.app.StatusBarManager; 25 import android.content.Context; 26 import android.content.pm.ResolveInfo; 27 import android.content.res.Configuration; 28 import android.graphics.Canvas; 29 import android.graphics.Color; 30 import android.graphics.Paint; 31 import android.graphics.Rect; 32 import android.util.AttributeSet; 33 import android.util.MathUtils; 34 import android.view.MotionEvent; 35 import android.view.VelocityTracker; 36 import android.view.View; 37 import android.view.ViewTreeObserver; 38 import android.view.WindowInsets; 39 import android.view.accessibility.AccessibilityEvent; 40 import android.widget.FrameLayout; 41 import android.widget.TextView; 42 43 import com.android.internal.logging.MetricsLogger; 44 import com.android.keyguard.KeyguardStatusView; 45 import com.android.systemui.AutoReinflateContainer; 46 import com.android.systemui.AutoReinflateContainer.InflateListener; 47 import com.android.systemui.DejankUtils; 48 import com.android.systemui.EventLogConstants; 49 import com.android.systemui.EventLogTags; 50 import com.android.systemui.Interpolators; 51 import com.android.systemui.R; 52 import com.android.systemui.classifier.FalsingManager; 53 import com.android.systemui.qs.QSContainer; 54 import com.android.systemui.statusbar.ExpandableNotificationRow; 55 import com.android.systemui.statusbar.ExpandableView; 56 import com.android.systemui.statusbar.FlingAnimationUtils; 57 import com.android.systemui.statusbar.GestureRecorder; 58 import com.android.systemui.statusbar.KeyguardAffordanceView; 59 import com.android.systemui.statusbar.NotificationData; 60 import com.android.systemui.statusbar.StatusBarState; 61 import com.android.systemui.statusbar.notification.NotificationUtils; 62 import com.android.systemui.statusbar.policy.HeadsUpManager; 63 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher; 64 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; 65 import com.android.systemui.statusbar.stack.StackStateAnimator; 66 67 import java.util.List; 68 69 public class NotificationPanelView extends PanelView implements 70 ExpandableView.OnHeightChangedListener, 71 View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener, 72 KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener, 73 HeadsUpManager.OnHeadsUpChangedListener { 74 75 private static final boolean DEBUG = false; 76 77 // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is 78 // changed. 79 private static final int CAP_HEIGHT = 1456; 80 private static final int FONT_HEIGHT = 2163; 81 82 private static final float LOCK_ICON_ACTIVE_SCALE = 1.2f; 83 84 static final String COUNTER_PANEL_OPEN = "panel_open"; 85 static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs"; 86 private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek"; 87 88 private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1); 89 90 public static final long DOZE_ANIMATION_DURATION = 700; 91 92 private KeyguardAffordanceHelper mAfforanceHelper; 93 private KeyguardUserSwitcher mKeyguardUserSwitcher; 94 private KeyguardStatusBarView mKeyguardStatusBar; 95 protected QSContainer mQsContainer; 96 private AutoReinflateContainer mQsAutoReinflateContainer; 97 private KeyguardStatusView mKeyguardStatusView; 98 private TextView mClockView; 99 private View mReserveNotificationSpace; 100 private View mQsNavbarScrim; 101 protected NotificationsQuickSettingsContainer mNotificationContainerParent; 102 protected NotificationStackScrollLayout mNotificationStackScroller; 103 private boolean mAnimateNextTopPaddingChange; 104 105 private int mTrackingPointer; 106 private VelocityTracker mVelocityTracker; 107 private boolean mQsTracking; 108 109 /** 110 * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and 111 * the expansion for quick settings. 112 */ 113 private boolean mConflictingQsExpansionGesture; 114 115 /** 116 * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't 117 * intercepted yet. 118 */ 119 private boolean mIntercepting; 120 private boolean mPanelExpanded; 121 private boolean mQsExpanded; 122 private boolean mQsExpandedWhenExpandingStarted; 123 private boolean mQsFullyExpanded; 124 private boolean mKeyguardShowing; 125 private boolean mDozing; 126 private boolean mDozingOnDown; 127 private int mStatusBarState; 128 private float mInitialHeightOnTouch; 129 private float mInitialTouchX; 130 private float mInitialTouchY; 131 private float mLastTouchX; 132 private float mLastTouchY; 133 protected float mQsExpansionHeight; 134 protected int mQsMinExpansionHeight; 135 protected int mQsMaxExpansionHeight; 136 private int mQsPeekHeight; 137 private boolean mStackScrollerOverscrolling; 138 private boolean mQsExpansionFromOverscroll; 139 private float mLastOverscroll; 140 protected boolean mQsExpansionEnabled = true; 141 private ValueAnimator mQsExpansionAnimator; 142 private FlingAnimationUtils mFlingAnimationUtils; 143 private int mStatusBarMinHeight; 144 private boolean mUnlockIconActive; 145 private int mNotificationsHeaderCollideDistance; 146 private int mUnlockMoveDistance; 147 private float mEmptyDragAmount; 148 149 private ObjectAnimator mClockAnimator; 150 private int mClockAnimationTarget = -1; 151 private int mTopPaddingAdjustment; 152 private KeyguardClockPositionAlgorithm mClockPositionAlgorithm = 153 new KeyguardClockPositionAlgorithm(); 154 private KeyguardClockPositionAlgorithm.Result mClockPositionResult = 155 new KeyguardClockPositionAlgorithm.Result(); 156 private boolean mIsExpanding; 157 158 private boolean mBlockTouches; 159 private int mNotificationScrimWaitDistance; 160 // Used for two finger gesture as well as accessibility shortcut to QS. 161 private boolean mQsExpandImmediate; 162 private boolean mTwoFingerQsExpandPossible; 163 164 /** 165 * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still 166 * need to take this into account in our panel height calculation. 167 */ 168 private boolean mQsAnimatorExpand; 169 private boolean mIsLaunchTransitionFinished; 170 private boolean mIsLaunchTransitionRunning; 171 private Runnable mLaunchAnimationEndRunnable; 172 private boolean mOnlyAffordanceInThisMotion; 173 private boolean mKeyguardStatusViewAnimating; 174 private ValueAnimator mQsSizeChangeAnimator; 175 176 private boolean mShadeEmpty; 177 178 private boolean mQsScrimEnabled = true; 179 private boolean mLastAnnouncementWasQuickSettings; 180 private boolean mQsTouchAboveFalsingThreshold; 181 private int mQsFalsingThreshold; 182 183 private float mKeyguardStatusBarAnimateAlpha = 1f; 184 private int mOldLayoutDirection; 185 private HeadsUpTouchHelper mHeadsUpTouchHelper; 186 private boolean mIsExpansionFromHeadsUp; 187 private boolean mListenForHeadsUp; 188 private int mNavigationBarBottomHeight; 189 private boolean mExpandingFromHeadsUp; 190 private boolean mCollapsedOnDown; 191 private int mPositionMinSideMargin; 192 private int mMaxFadeoutHeight; 193 private int mLastOrientation = -1; 194 private boolean mClosingWithAlphaFadeOut; 195 private boolean mHeadsUpAnimatingAway; 196 private boolean mLaunchingAffordance; 197 private FalsingManager mFalsingManager; 198 private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE; 199 200 private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() { 201 @Override 202 public void run() { 203 mHeadsUpAnimatingAway = false; 204 notifyBarPanelExpansionChanged(); 205 } 206 }; 207 private NotificationGroupManager mGroupManager; 208 209 public NotificationPanelView(Context context, AttributeSet attrs) { 210 super(context, attrs); 211 setWillNotDraw(!DEBUG); 212 mFalsingManager = FalsingManager.getInstance(context); 213 } 214 215 public void setStatusBar(PhoneStatusBar bar) { 216 mStatusBar = bar; 217 } 218 219 @Override 220 protected void onFinishInflate() { 221 super.onFinishInflate(); 222 mKeyguardStatusBar = (KeyguardStatusBarView) findViewById(R.id.keyguard_header); 223 mKeyguardStatusView = (KeyguardStatusView) findViewById(R.id.keyguard_status_view); 224 mClockView = (TextView) findViewById(R.id.clock_view); 225 226 mNotificationContainerParent = (NotificationsQuickSettingsContainer) 227 findViewById(R.id.notification_container_parent); 228 mNotificationStackScroller = (NotificationStackScrollLayout) 229 findViewById(R.id.notification_stack_scroller); 230 mNotificationStackScroller.setOnHeightChangedListener(this); 231 mNotificationStackScroller.setOverscrollTopChangedListener(this); 232 mNotificationStackScroller.setOnEmptySpaceClickListener(this); 233 mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); 234 mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim); 235 mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext()); 236 mKeyguardBottomArea.setAffordanceHelper(mAfforanceHelper); 237 mLastOrientation = getResources().getConfiguration().orientation; 238 239 mQsAutoReinflateContainer = 240 (AutoReinflateContainer) findViewById(R.id.qs_auto_reinflate_container); 241 mQsAutoReinflateContainer.addInflateListener(new InflateListener() { 242 @Override 243 public void onInflated(View v) { 244 mQsContainer = (QSContainer) v.findViewById(R.id.quick_settings_container); 245 mQsContainer.setPanelView(NotificationPanelView.this); 246 mQsContainer.getHeader().findViewById(R.id.expand_indicator) 247 .setOnClickListener(NotificationPanelView.this); 248 249 // recompute internal state when qspanel height changes 250 mQsContainer.addOnLayoutChangeListener(new OnLayoutChangeListener() { 251 @Override 252 public void onLayoutChange(View v, int left, int top, int right, int bottom, 253 int oldLeft, int oldTop, int oldRight, int oldBottom) { 254 final int height = bottom - top; 255 final int oldHeight = oldBottom - oldTop; 256 if (height != oldHeight) { 257 onQsHeightChanged(); 258 } 259 } 260 }); 261 mNotificationStackScroller.setQsContainer(mQsContainer); 262 } 263 }); 264 } 265 266 @Override 267 protected void loadDimens() { 268 super.loadDimens(); 269 mFlingAnimationUtils = new FlingAnimationUtils(getContext(), 0.4f); 270 mStatusBarMinHeight = getResources().getDimensionPixelSize( 271 com.android.internal.R.dimen.status_bar_height); 272 mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height); 273 mNotificationsHeaderCollideDistance = 274 getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance); 275 mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance); 276 mClockPositionAlgorithm.loadDimens(getResources()); 277 mNotificationScrimWaitDistance = 278 getResources().getDimensionPixelSize(R.dimen.notification_scrim_wait_distance); 279 mQsFalsingThreshold = getResources().getDimensionPixelSize( 280 R.dimen.qs_falsing_threshold); 281 mPositionMinSideMargin = getResources().getDimensionPixelSize( 282 R.dimen.notification_panel_min_side_margin); 283 mMaxFadeoutHeight = getResources().getDimensionPixelSize( 284 R.dimen.max_notification_fadeout_height); 285 } 286 287 public void updateResources() { 288 int panelWidth = getResources().getDimensionPixelSize(R.dimen.notification_panel_width); 289 int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity); 290 FrameLayout.LayoutParams lp = 291 (FrameLayout.LayoutParams) mQsAutoReinflateContainer.getLayoutParams(); 292 if (lp.width != panelWidth) { 293 lp.width = panelWidth; 294 lp.gravity = panelGravity; 295 mQsAutoReinflateContainer.setLayoutParams(lp); 296 mQsContainer.post(mUpdateHeader); 297 } 298 299 lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams(); 300 if (lp.width != panelWidth) { 301 lp.width = panelWidth; 302 lp.gravity = panelGravity; 303 mNotificationStackScroller.setLayoutParams(lp); 304 } 305 } 306 307 @Override 308 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 309 super.onLayout(changed, left, top, right, bottom); 310 311 // Update Clock Pivot 312 mKeyguardStatusView.setPivotX(getWidth() / 2); 313 mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f * mClockView.getTextSize()); 314 315 // Calculate quick setting heights. 316 int oldMaxHeight = mQsMaxExpansionHeight; 317 mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQsContainer.getQsMinExpansionHeight(); 318 mQsMaxExpansionHeight = mQsContainer.getDesiredHeight(); 319 positionClockAndNotifications(); 320 if (mQsExpanded && mQsFullyExpanded) { 321 mQsExpansionHeight = mQsMaxExpansionHeight; 322 requestScrollerTopPaddingUpdate(false /* animate */); 323 requestPanelHeightUpdate(); 324 325 // Size has changed, start an animation. 326 if (mQsMaxExpansionHeight != oldMaxHeight) { 327 startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight); 328 } 329 } else if (!mQsExpanded) { 330 setQsExpansion(mQsMinExpansionHeight + mLastOverscroll); 331 } 332 updateExpandedHeight(getExpandedHeight()); 333 updateHeader(); 334 335 // If we are running a size change animation, the animation takes care of the height of 336 // the container. However, if we are not animating, we always need to make the QS container 337 // the desired height so when closing the QS detail, it stays smaller after the size change 338 // animation is finished but the detail view is still being animated away (this animation 339 // takes longer than the size change animation). 340 if (mQsSizeChangeAnimator == null) { 341 mQsContainer.setHeightOverride(mQsContainer.getDesiredHeight()); 342 } 343 updateMaxHeadsUpTranslation(); 344 } 345 346 private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) { 347 if (mQsSizeChangeAnimator != null) { 348 oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue(); 349 mQsSizeChangeAnimator.cancel(); 350 } 351 mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight); 352 mQsSizeChangeAnimator.setDuration(300); 353 mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 354 mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 355 @Override 356 public void onAnimationUpdate(ValueAnimator animation) { 357 requestScrollerTopPaddingUpdate(false /* animate */); 358 requestPanelHeightUpdate(); 359 int height = (int) mQsSizeChangeAnimator.getAnimatedValue(); 360 mQsContainer.setHeightOverride(height); 361 } 362 }); 363 mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() { 364 @Override 365 public void onAnimationEnd(Animator animation) { 366 mQsSizeChangeAnimator = null; 367 } 368 }); 369 mQsSizeChangeAnimator.start(); 370 } 371 372 /** 373 * Positions the clock and notifications dynamically depending on how many notifications are 374 * showing. 375 */ 376 private void positionClockAndNotifications() { 377 boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending(); 378 int stackScrollerPadding; 379 if (mStatusBarState != StatusBarState.KEYGUARD) { 380 stackScrollerPadding = mQsContainer.getHeader().getHeight() + mQsPeekHeight; 381 mTopPaddingAdjustment = 0; 382 } else { 383 mClockPositionAlgorithm.setup( 384 mStatusBar.getMaxKeyguardNotifications(), 385 getMaxPanelHeight(), 386 getExpandedHeight(), 387 mNotificationStackScroller.getNotGoneChildCount(), 388 getHeight(), 389 mKeyguardStatusView.getHeight(), 390 mEmptyDragAmount); 391 mClockPositionAlgorithm.run(mClockPositionResult); 392 if (animate || mClockAnimator != null) { 393 startClockAnimation(mClockPositionResult.clockY); 394 } else { 395 mKeyguardStatusView.setY(mClockPositionResult.clockY); 396 } 397 updateClock(mClockPositionResult.clockAlpha, mClockPositionResult.clockScale); 398 stackScrollerPadding = mClockPositionResult.stackScrollerPadding; 399 mTopPaddingAdjustment = mClockPositionResult.stackScrollerPaddingAdjustment; 400 } 401 mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding); 402 requestScrollerTopPaddingUpdate(animate); 403 } 404 405 /** 406 * @param maximum the maximum to return at most 407 * @return the maximum keyguard notifications that can fit on the screen 408 */ 409 public int computeMaxKeyguardNotifications(int maximum) { 410 float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding(getHeight(), 411 mKeyguardStatusView.getHeight()); 412 int notificationPadding = Math.max(1, getResources().getDimensionPixelSize( 413 R.dimen.notification_divider_height)); 414 final int overflowheight = getResources().getDimensionPixelSize( 415 R.dimen.notification_summary_height); 416 float bottomStackSize = mNotificationStackScroller.getKeyguardBottomStackSize(); 417 float availableSpace = mNotificationStackScroller.getHeight() - minPadding - overflowheight 418 - bottomStackSize; 419 int count = 0; 420 for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) { 421 ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i); 422 if (!(child instanceof ExpandableNotificationRow)) { 423 continue; 424 } 425 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 426 boolean suppressedSummary = mGroupManager.isSummaryOfSuppressedGroup( 427 row.getStatusBarNotification()); 428 if (suppressedSummary) { 429 continue; 430 } 431 if (!mStatusBar.shouldShowOnKeyguard(row.getStatusBarNotification())) { 432 continue; 433 } 434 if (row.isRemoved()) { 435 continue; 436 } 437 availableSpace -= child.getMinHeight() + notificationPadding; 438 if (availableSpace >= 0 && count < maximum) { 439 count++; 440 } else { 441 return count; 442 } 443 } 444 return count; 445 } 446 447 private void startClockAnimation(int y) { 448 if (mClockAnimationTarget == y) { 449 return; 450 } 451 mClockAnimationTarget = y; 452 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 453 @Override 454 public boolean onPreDraw() { 455 getViewTreeObserver().removeOnPreDrawListener(this); 456 if (mClockAnimator != null) { 457 mClockAnimator.removeAllListeners(); 458 mClockAnimator.cancel(); 459 } 460 mClockAnimator = ObjectAnimator 461 .ofFloat(mKeyguardStatusView, View.Y, mClockAnimationTarget); 462 mClockAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 463 mClockAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 464 mClockAnimator.addListener(new AnimatorListenerAdapter() { 465 @Override 466 public void onAnimationEnd(Animator animation) { 467 mClockAnimator = null; 468 mClockAnimationTarget = -1; 469 } 470 }); 471 mClockAnimator.start(); 472 return true; 473 } 474 }); 475 } 476 477 private void updateClock(float alpha, float scale) { 478 if (!mKeyguardStatusViewAnimating) { 479 mKeyguardStatusView.setAlpha(alpha); 480 } 481 mKeyguardStatusView.setScaleX(scale); 482 mKeyguardStatusView.setScaleY(scale); 483 } 484 485 public void animateToFullShade(long delay) { 486 mAnimateNextTopPaddingChange = true; 487 mNotificationStackScroller.goToFullShade(delay); 488 requestLayout(); 489 } 490 491 public void setQsExpansionEnabled(boolean qsExpansionEnabled) { 492 mQsExpansionEnabled = qsExpansionEnabled; 493 mQsContainer.setHeaderClickable(qsExpansionEnabled); 494 } 495 496 @Override 497 public void resetViews() { 498 mIsLaunchTransitionFinished = false; 499 mBlockTouches = false; 500 mUnlockIconActive = false; 501 if (!mLaunchingAffordance) { 502 mAfforanceHelper.reset(false); 503 mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE; 504 } 505 closeQs(); 506 mStatusBar.dismissPopups(); 507 mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, false /* animate */, 508 true /* cancelAnimators */); 509 mNotificationStackScroller.resetScrollPosition(); 510 } 511 512 public void closeQs() { 513 cancelQsAnimation(); 514 setQsExpansion(mQsMinExpansionHeight); 515 } 516 517 public void animateCloseQs() { 518 if (mQsExpansionAnimator != null) { 519 if (!mQsAnimatorExpand) { 520 return; 521 } 522 float height = mQsExpansionHeight; 523 mQsExpansionAnimator.cancel(); 524 setQsExpansion(height); 525 } 526 flingSettings(0 /* vel */, false); 527 } 528 529 public void openQs() { 530 cancelQsAnimation(); 531 if (mQsExpansionEnabled) { 532 setQsExpansion(mQsMaxExpansionHeight); 533 } 534 } 535 536 public void expandWithQs() { 537 if (mQsExpansionEnabled) { 538 mQsExpandImmediate = true; 539 } 540 expand(true /* animate */); 541 } 542 543 @Override 544 public void fling(float vel, boolean expand) { 545 GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder(); 546 if (gr != null) { 547 gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel); 548 } 549 super.fling(vel, expand); 550 } 551 552 @Override 553 protected void flingToHeight(float vel, boolean expand, float target, 554 float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) { 555 mHeadsUpTouchHelper.notifyFling(!expand); 556 setClosingWithAlphaFadeout(!expand 557 && mNotificationStackScroller.getFirstChildIntrinsicHeight() <= mMaxFadeoutHeight 558 && getFadeoutAlpha() == 1.0f); 559 super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing); 560 } 561 562 @Override 563 public boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) { 564 if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 565 event.getText().add(getKeyguardOrLockScreenString()); 566 mLastAnnouncementWasQuickSettings = false; 567 return true; 568 } 569 return super.dispatchPopulateAccessibilityEventInternal(event); 570 } 571 572 @Override 573 public boolean onInterceptTouchEvent(MotionEvent event) { 574 if (mBlockTouches || mQsContainer.isCustomizing()) { 575 return false; 576 } 577 initDownStates(event); 578 if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { 579 mIsExpansionFromHeadsUp = true; 580 MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1); 581 MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1); 582 return true; 583 } 584 if (!isFullyCollapsed() && onQsIntercept(event)) { 585 return true; 586 } 587 return super.onInterceptTouchEvent(event); 588 } 589 590 private boolean onQsIntercept(MotionEvent event) { 591 int pointerIndex = event.findPointerIndex(mTrackingPointer); 592 if (pointerIndex < 0) { 593 pointerIndex = 0; 594 mTrackingPointer = event.getPointerId(pointerIndex); 595 } 596 final float x = event.getX(pointerIndex); 597 final float y = event.getY(pointerIndex); 598 599 switch (event.getActionMasked()) { 600 case MotionEvent.ACTION_DOWN: 601 mIntercepting = true; 602 mInitialTouchY = y; 603 mInitialTouchX = x; 604 initVelocityTracker(); 605 trackMovement(event); 606 if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) { 607 getParent().requestDisallowInterceptTouchEvent(true); 608 } 609 if (mQsExpansionAnimator != null) { 610 onQsExpansionStarted(); 611 mInitialHeightOnTouch = mQsExpansionHeight; 612 mQsTracking = true; 613 mIntercepting = false; 614 mNotificationStackScroller.removeLongPressCallback(); 615 } 616 break; 617 case MotionEvent.ACTION_POINTER_UP: 618 final int upPointer = event.getPointerId(event.getActionIndex()); 619 if (mTrackingPointer == upPointer) { 620 // gesture is ongoing, find a new pointer to track 621 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 622 mTrackingPointer = event.getPointerId(newIndex); 623 mInitialTouchX = event.getX(newIndex); 624 mInitialTouchY = event.getY(newIndex); 625 } 626 break; 627 628 case MotionEvent.ACTION_MOVE: 629 final float h = y - mInitialTouchY; 630 trackMovement(event); 631 if (mQsTracking) { 632 633 // Already tracking because onOverscrolled was called. We need to update here 634 // so we don't stop for a frame until the next touch event gets handled in 635 // onTouchEvent. 636 setQsExpansion(h + mInitialHeightOnTouch); 637 trackMovement(event); 638 mIntercepting = false; 639 return true; 640 } 641 if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX) 642 && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) { 643 mQsTracking = true; 644 onQsExpansionStarted(); 645 notifyExpandingFinished(); 646 mInitialHeightOnTouch = mQsExpansionHeight; 647 mInitialTouchY = y; 648 mInitialTouchX = x; 649 mIntercepting = false; 650 mNotificationStackScroller.removeLongPressCallback(); 651 return true; 652 } 653 break; 654 655 case MotionEvent.ACTION_CANCEL: 656 case MotionEvent.ACTION_UP: 657 trackMovement(event); 658 if (mQsTracking) { 659 flingQsWithCurrentVelocity(y, 660 event.getActionMasked() == MotionEvent.ACTION_CANCEL); 661 mQsTracking = false; 662 } 663 mIntercepting = false; 664 break; 665 } 666 return false; 667 } 668 669 @Override 670 protected boolean isInContentBounds(float x, float y) { 671 float stackScrollerX = mNotificationStackScroller.getX(); 672 return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y) 673 && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth(); 674 } 675 676 private void initDownStates(MotionEvent event) { 677 if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 678 mOnlyAffordanceInThisMotion = false; 679 mQsTouchAboveFalsingThreshold = mQsFullyExpanded; 680 mDozingOnDown = isDozing(); 681 mCollapsedOnDown = isFullyCollapsed(); 682 mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp(); 683 } 684 } 685 686 private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) { 687 float vel = getCurrentVelocity(); 688 final boolean expandsQs = flingExpandsQs(vel); 689 if (expandsQs) { 690 logQsSwipeDown(y); 691 } 692 flingSettings(vel, expandsQs && !isCancelMotionEvent); 693 } 694 695 private void logQsSwipeDown(float y) { 696 float vel = getCurrentVelocity(); 697 final int gesture = mStatusBarState == StatusBarState.KEYGUARD 698 ? EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DOWN_QS 699 : EventLogConstants.SYSUI_SHADE_GESTURE_SWIPE_DOWN_QS; 700 EventLogTags.writeSysuiLockscreenGesture( 701 gesture, 702 (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()), 703 (int) (vel / mStatusBar.getDisplayDensity())); 704 } 705 706 private boolean flingExpandsQs(float vel) { 707 if (isFalseTouch()) { 708 return false; 709 } 710 if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) { 711 return getQsExpansionFraction() > 0.5f; 712 } else { 713 return vel > 0; 714 } 715 } 716 717 private boolean isFalseTouch() { 718 if (!needsAntiFalsing()) { 719 return false; 720 } 721 if (mFalsingManager.isClassiferEnabled()) { 722 return mFalsingManager.isFalseTouch(); 723 } 724 return !mQsTouchAboveFalsingThreshold; 725 } 726 727 private float getQsExpansionFraction() { 728 return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight) 729 / (getTempQsMaxExpansion() - mQsMinExpansionHeight)); 730 } 731 732 @Override 733 public boolean onTouchEvent(MotionEvent event) { 734 if (mBlockTouches || mQsContainer.isCustomizing()) { 735 return false; 736 } 737 initDownStates(event); 738 if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp() 739 && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) { 740 mIsExpansionFromHeadsUp = true; 741 MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1); 742 } 743 if ((!mIsExpanding || mHintAnimationRunning) 744 && !mQsExpanded 745 && mStatusBar.getBarState() != StatusBarState.SHADE) { 746 mAfforanceHelper.onTouchEvent(event); 747 } 748 if (mOnlyAffordanceInThisMotion) { 749 return true; 750 } 751 mHeadsUpTouchHelper.onTouchEvent(event); 752 if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) { 753 return true; 754 } 755 if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) { 756 MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1); 757 updateVerticalPanelPosition(event.getX()); 758 } 759 super.onTouchEvent(event); 760 return true; 761 } 762 763 private boolean handleQsTouch(MotionEvent event) { 764 final int action = event.getActionMasked(); 765 if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f 766 && mStatusBar.getBarState() != StatusBarState.KEYGUARD && !mQsExpanded 767 && mQsExpansionEnabled) { 768 769 // Down in the empty area while fully expanded - go to QS. 770 mQsTracking = true; 771 mConflictingQsExpansionGesture = true; 772 onQsExpansionStarted(); 773 mInitialHeightOnTouch = mQsExpansionHeight; 774 mInitialTouchY = event.getX(); 775 mInitialTouchX = event.getY(); 776 } 777 if (!isFullyCollapsed()) { 778 handleQsDown(event); 779 } 780 if (!mQsExpandImmediate && mQsTracking) { 781 onQsTouch(event); 782 if (!mConflictingQsExpansionGesture) { 783 return true; 784 } 785 } 786 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 787 mConflictingQsExpansionGesture = false; 788 } 789 if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed() 790 && mQsExpansionEnabled) { 791 mTwoFingerQsExpandPossible = true; 792 } 793 if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) 794 && event.getY(event.getActionIndex()) < mStatusBarMinHeight) { 795 MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1); 796 mQsExpandImmediate = true; 797 requestPanelHeightUpdate(); 798 799 // Normally, we start listening when the panel is expanded, but here we need to start 800 // earlier so the state is already up to date when dragging down. 801 setListening(true); 802 } 803 return false; 804 } 805 806 private boolean isInQsArea(float x, float y) { 807 return (x >= mQsAutoReinflateContainer.getX() 808 && x <= mQsAutoReinflateContainer.getX() + mQsAutoReinflateContainer.getWidth()) 809 && (y <= mNotificationStackScroller.getBottomMostNotificationBottom() 810 || y <= mQsContainer.getY() + mQsContainer.getHeight()); 811 } 812 813 private boolean isOpenQsEvent(MotionEvent event) { 814 final int pointerCount = event.getPointerCount(); 815 final int action = event.getActionMasked(); 816 817 final boolean twoFingerDrag = action == MotionEvent.ACTION_POINTER_DOWN 818 && pointerCount == 2; 819 820 final boolean stylusButtonClickDrag = action == MotionEvent.ACTION_DOWN 821 && (event.isButtonPressed(MotionEvent.BUTTON_STYLUS_PRIMARY) 822 || event.isButtonPressed(MotionEvent.BUTTON_STYLUS_SECONDARY)); 823 824 final boolean mouseButtonClickDrag = action == MotionEvent.ACTION_DOWN 825 && (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY) 826 || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY)); 827 828 return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag; 829 } 830 831 private void handleQsDown(MotionEvent event) { 832 if (event.getActionMasked() == MotionEvent.ACTION_DOWN 833 && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) { 834 mFalsingManager.onQsDown(); 835 mQsTracking = true; 836 onQsExpansionStarted(); 837 mInitialHeightOnTouch = mQsExpansionHeight; 838 mInitialTouchY = event.getX(); 839 mInitialTouchX = event.getY(); 840 841 // If we interrupt an expansion gesture here, make sure to update the state correctly. 842 notifyExpandingFinished(); 843 } 844 } 845 846 @Override 847 protected boolean flingExpands(float vel, float vectorVel, float x, float y) { 848 boolean expands = super.flingExpands(vel, vectorVel, x, y); 849 850 // If we are already running a QS expansion, make sure that we keep the panel open. 851 if (mQsExpansionAnimator != null) { 852 expands = true; 853 } 854 return expands; 855 } 856 857 @Override 858 protected boolean hasConflictingGestures() { 859 return mStatusBar.getBarState() != StatusBarState.SHADE; 860 } 861 862 @Override 863 protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) { 864 return !mAfforanceHelper.isOnAffordanceIcon(x, y); 865 } 866 867 private void onQsTouch(MotionEvent event) { 868 int pointerIndex = event.findPointerIndex(mTrackingPointer); 869 if (pointerIndex < 0) { 870 pointerIndex = 0; 871 mTrackingPointer = event.getPointerId(pointerIndex); 872 } 873 final float y = event.getY(pointerIndex); 874 final float x = event.getX(pointerIndex); 875 final float h = y - mInitialTouchY; 876 877 switch (event.getActionMasked()) { 878 case MotionEvent.ACTION_DOWN: 879 mQsTracking = true; 880 mInitialTouchY = y; 881 mInitialTouchX = x; 882 onQsExpansionStarted(); 883 mInitialHeightOnTouch = mQsExpansionHeight; 884 initVelocityTracker(); 885 trackMovement(event); 886 break; 887 888 case MotionEvent.ACTION_POINTER_UP: 889 final int upPointer = event.getPointerId(event.getActionIndex()); 890 if (mTrackingPointer == upPointer) { 891 // gesture is ongoing, find a new pointer to track 892 final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; 893 final float newY = event.getY(newIndex); 894 final float newX = event.getX(newIndex); 895 mTrackingPointer = event.getPointerId(newIndex); 896 mInitialHeightOnTouch = mQsExpansionHeight; 897 mInitialTouchY = newY; 898 mInitialTouchX = newX; 899 } 900 break; 901 902 case MotionEvent.ACTION_MOVE: 903 setQsExpansion(h + mInitialHeightOnTouch); 904 if (h >= getFalsingThreshold()) { 905 mQsTouchAboveFalsingThreshold = true; 906 } 907 trackMovement(event); 908 break; 909 910 case MotionEvent.ACTION_UP: 911 case MotionEvent.ACTION_CANCEL: 912 mQsTracking = false; 913 mTrackingPointer = -1; 914 trackMovement(event); 915 float fraction = getQsExpansionFraction(); 916 if (fraction != 0f || y >= mInitialTouchY) { 917 flingQsWithCurrentVelocity(y, 918 event.getActionMasked() == MotionEvent.ACTION_CANCEL); 919 } 920 if (mVelocityTracker != null) { 921 mVelocityTracker.recycle(); 922 mVelocityTracker = null; 923 } 924 break; 925 } 926 } 927 928 private int getFalsingThreshold() { 929 float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f; 930 return (int) (mQsFalsingThreshold * factor); 931 } 932 933 @Override 934 public void onOverscrollTopChanged(float amount, boolean isRubberbanded) { 935 cancelQsAnimation(); 936 if (!mQsExpansionEnabled) { 937 amount = 0f; 938 } 939 float rounded = amount >= 1f ? amount : 0f; 940 setOverScrolling(rounded != 0f && isRubberbanded); 941 mQsExpansionFromOverscroll = rounded != 0f; 942 mLastOverscroll = rounded; 943 updateQsState(); 944 setQsExpansion(mQsMinExpansionHeight + rounded); 945 } 946 947 @Override 948 public void flingTopOverscroll(float velocity, boolean open) { 949 mLastOverscroll = 0f; 950 mQsExpansionFromOverscroll = false; 951 setQsExpansion(mQsExpansionHeight); 952 flingSettings(!mQsExpansionEnabled && open ? 0f : velocity, open && mQsExpansionEnabled, 953 new Runnable() { 954 @Override 955 public void run() { 956 mStackScrollerOverscrolling = false; 957 setOverScrolling(false); 958 updateQsState(); 959 } 960 }, false /* isClick */); 961 } 962 963 private void setOverScrolling(boolean overscrolling) { 964 mStackScrollerOverscrolling = overscrolling; 965 mQsContainer.setOverscrolling(overscrolling); 966 } 967 968 private void onQsExpansionStarted() { 969 onQsExpansionStarted(0); 970 } 971 972 private void onQsExpansionStarted(int overscrollAmount) { 973 cancelQsAnimation(); 974 cancelHeightAnimator(); 975 976 // Reset scroll position and apply that position to the expanded height. 977 float height = mQsExpansionHeight - overscrollAmount; 978 setQsExpansion(height); 979 requestPanelHeightUpdate(); 980 } 981 982 private void setQsExpanded(boolean expanded) { 983 boolean changed = mQsExpanded != expanded; 984 if (changed) { 985 mQsExpanded = expanded; 986 updateQsState(); 987 requestPanelHeightUpdate(); 988 mFalsingManager.setQsExpanded(expanded); 989 mStatusBar.setQsExpanded(expanded); 990 mNotificationContainerParent.setQsExpanded(expanded); 991 } 992 } 993 994 public void setBarState(int statusBarState, boolean keyguardFadingAway, 995 boolean goingToFullShade) { 996 int oldState = mStatusBarState; 997 boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD; 998 setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade); 999 setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade); 1000 1001 mStatusBarState = statusBarState; 1002 mKeyguardShowing = keyguardShowing; 1003 mQsContainer.setKeyguardShowing(mKeyguardShowing); 1004 1005 if (oldState == StatusBarState.KEYGUARD 1006 && (goingToFullShade || statusBarState == StatusBarState.SHADE_LOCKED)) { 1007 animateKeyguardStatusBarOut(); 1008 long delay = mStatusBarState == StatusBarState.SHADE_LOCKED 1009 ? 0 : mStatusBar.calculateGoingToFullShadeDelay(); 1010 mQsContainer.animateHeaderSlidingIn(delay); 1011 } else if (oldState == StatusBarState.SHADE_LOCKED 1012 && statusBarState == StatusBarState.KEYGUARD) { 1013 animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD); 1014 mQsContainer.animateHeaderSlidingOut(); 1015 } else { 1016 mKeyguardStatusBar.setAlpha(1f); 1017 mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE); 1018 if (keyguardShowing && oldState != mStatusBarState) { 1019 mKeyguardBottomArea.onKeyguardShowingChanged(); 1020 mQsContainer.hideImmediately(); 1021 } 1022 } 1023 if (keyguardShowing) { 1024 updateDozingVisibilities(false /* animate */); 1025 } 1026 resetVerticalPanelPosition(); 1027 updateQsState(); 1028 } 1029 1030 private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() { 1031 @Override 1032 public void run() { 1033 mKeyguardStatusViewAnimating = false; 1034 mKeyguardStatusView.setVisibility(View.GONE); 1035 } 1036 }; 1037 1038 private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() { 1039 @Override 1040 public void run() { 1041 mKeyguardStatusViewAnimating = false; 1042 } 1043 }; 1044 1045 private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() { 1046 @Override 1047 public void run() { 1048 mKeyguardStatusBar.setVisibility(View.INVISIBLE); 1049 mKeyguardStatusBar.setAlpha(1f); 1050 mKeyguardStatusBarAnimateAlpha = 1f; 1051 } 1052 }; 1053 1054 private void animateKeyguardStatusBarOut() { 1055 ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f); 1056 anim.addUpdateListener(mStatusBarAnimateAlphaListener); 1057 anim.setStartDelay(mStatusBar.isKeyguardFadingAway() 1058 ? mStatusBar.getKeyguardFadingAwayDelay() 1059 : 0); 1060 anim.setDuration(mStatusBar.isKeyguardFadingAway() 1061 ? mStatusBar.getKeyguardFadingAwayDuration() / 2 1062 : StackStateAnimator.ANIMATION_DURATION_STANDARD); 1063 anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 1064 anim.addListener(new AnimatorListenerAdapter() { 1065 @Override 1066 public void onAnimationEnd(Animator animation) { 1067 mAnimateKeyguardStatusBarInvisibleEndRunnable.run(); 1068 } 1069 }); 1070 anim.start(); 1071 } 1072 1073 private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener = 1074 new ValueAnimator.AnimatorUpdateListener() { 1075 @Override 1076 public void onAnimationUpdate(ValueAnimator animation) { 1077 mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue(); 1078 updateHeaderKeyguardAlpha(); 1079 } 1080 }; 1081 1082 private void animateKeyguardStatusBarIn(long duration) { 1083 mKeyguardStatusBar.setVisibility(View.VISIBLE); 1084 mKeyguardStatusBar.setAlpha(0f); 1085 ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f); 1086 anim.addUpdateListener(mStatusBarAnimateAlphaListener); 1087 anim.setDuration(duration); 1088 anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN); 1089 anim.start(); 1090 } 1091 1092 private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() { 1093 @Override 1094 public void run() { 1095 mKeyguardBottomArea.setVisibility(View.GONE); 1096 } 1097 }; 1098 1099 private void setKeyguardBottomAreaVisibility(int statusBarState, 1100 boolean goingToFullShade) { 1101 mKeyguardBottomArea.animate().cancel(); 1102 if (goingToFullShade) { 1103 mKeyguardBottomArea.animate() 1104 .alpha(0f) 1105 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 1106 .setDuration(mStatusBar.getKeyguardFadingAwayDuration() / 2) 1107 .setInterpolator(Interpolators.ALPHA_OUT) 1108 .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable) 1109 .start(); 1110 } else if (statusBarState == StatusBarState.KEYGUARD 1111 || statusBarState == StatusBarState.SHADE_LOCKED) { 1112 if (!mDozing) { 1113 mKeyguardBottomArea.setVisibility(View.VISIBLE); 1114 } 1115 mKeyguardBottomArea.setAlpha(1f); 1116 } else { 1117 mKeyguardBottomArea.setVisibility(View.GONE); 1118 mKeyguardBottomArea.setAlpha(1f); 1119 } 1120 } 1121 1122 private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway, 1123 boolean goingToFullShade) { 1124 if ((!keyguardFadingAway && mStatusBarState == StatusBarState.KEYGUARD 1125 && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) { 1126 mKeyguardStatusView.animate().cancel(); 1127 mKeyguardStatusViewAnimating = true; 1128 mKeyguardStatusView.animate() 1129 .alpha(0f) 1130 .setStartDelay(0) 1131 .setDuration(160) 1132 .setInterpolator(Interpolators.ALPHA_OUT) 1133 .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable); 1134 if (keyguardFadingAway) { 1135 mKeyguardStatusView.animate() 1136 .setStartDelay(mStatusBar.getKeyguardFadingAwayDelay()) 1137 .setDuration(mStatusBar.getKeyguardFadingAwayDuration()/2) 1138 .start(); 1139 } 1140 } else if (mStatusBarState == StatusBarState.SHADE_LOCKED 1141 && statusBarState == StatusBarState.KEYGUARD) { 1142 mKeyguardStatusView.animate().cancel(); 1143 mKeyguardStatusView.setVisibility(View.VISIBLE); 1144 mKeyguardStatusViewAnimating = true; 1145 mKeyguardStatusView.setAlpha(0f); 1146 mKeyguardStatusView.animate() 1147 .alpha(1f) 1148 .setStartDelay(0) 1149 .setDuration(320) 1150 .setInterpolator(Interpolators.ALPHA_IN) 1151 .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable); 1152 } else if (statusBarState == StatusBarState.KEYGUARD) { 1153 mKeyguardStatusView.animate().cancel(); 1154 mKeyguardStatusViewAnimating = false; 1155 mKeyguardStatusView.setVisibility(View.VISIBLE); 1156 mKeyguardStatusView.setAlpha(1f); 1157 } else { 1158 mKeyguardStatusView.animate().cancel(); 1159 mKeyguardStatusViewAnimating = false; 1160 mKeyguardStatusView.setVisibility(View.GONE); 1161 mKeyguardStatusView.setAlpha(1f); 1162 } 1163 } 1164 1165 private void updateQsState() { 1166 mQsContainer.setExpanded(mQsExpanded); 1167 mNotificationStackScroller.setQsExpanded(mQsExpanded); 1168 mNotificationStackScroller.setScrollingEnabled( 1169 mStatusBarState != StatusBarState.KEYGUARD && (!mQsExpanded 1170 || mQsExpansionFromOverscroll)); 1171 updateEmptyShadeView(); 1172 mQsNavbarScrim.setVisibility(mStatusBarState == StatusBarState.SHADE && mQsExpanded 1173 && !mStackScrollerOverscrolling && mQsScrimEnabled 1174 ? View.VISIBLE 1175 : View.INVISIBLE); 1176 if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) { 1177 mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */); 1178 } 1179 } 1180 1181 private void setQsExpansion(float height) { 1182 height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight); 1183 mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0; 1184 if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling) { 1185 setQsExpanded(true); 1186 } else if (height <= mQsMinExpansionHeight && mQsExpanded) { 1187 setQsExpanded(false); 1188 if (mLastAnnouncementWasQuickSettings && !mTracking && !isCollapsing()) { 1189 announceForAccessibility(getKeyguardOrLockScreenString()); 1190 mLastAnnouncementWasQuickSettings = false; 1191 } 1192 } 1193 mQsExpansionHeight = height; 1194 updateQsExpansion(); 1195 requestScrollerTopPaddingUpdate(false /* animate */); 1196 if (mKeyguardShowing) { 1197 updateHeaderKeyguardAlpha(); 1198 } 1199 if (mStatusBarState == StatusBarState.SHADE_LOCKED 1200 || mStatusBarState == StatusBarState.KEYGUARD) { 1201 updateKeyguardBottomAreaAlpha(); 1202 } 1203 if (mStatusBarState == StatusBarState.SHADE && mQsExpanded 1204 && !mStackScrollerOverscrolling && mQsScrimEnabled) { 1205 mQsNavbarScrim.setAlpha(getQsExpansionFraction()); 1206 } 1207 1208 // Upon initialisation when we are not layouted yet we don't want to announce that we are 1209 // fully expanded, hence the != 0.0f check. 1210 if (height != 0.0f && mQsFullyExpanded && !mLastAnnouncementWasQuickSettings) { 1211 announceForAccessibility(getContext().getString( 1212 R.string.accessibility_desc_quick_settings)); 1213 mLastAnnouncementWasQuickSettings = true; 1214 } 1215 if (mQsFullyExpanded && mFalsingManager.shouldEnforceBouncer()) { 1216 mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */, 1217 false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */); 1218 } 1219 if (DEBUG) { 1220 invalidate(); 1221 } 1222 } 1223 1224 protected void updateQsExpansion() { 1225 mQsContainer.setQsExpansion(getQsExpansionFraction(), getHeaderTranslation()); 1226 } 1227 1228 private String getKeyguardOrLockScreenString() { 1229 if (mQsContainer.isCustomizing()) { 1230 return getContext().getString(R.string.accessibility_desc_quick_settings_edit); 1231 } else if (mStatusBarState == StatusBarState.KEYGUARD) { 1232 return getContext().getString(R.string.accessibility_desc_lock_screen); 1233 } else { 1234 return getContext().getString(R.string.accessibility_desc_notification_shade); 1235 } 1236 } 1237 1238 private float calculateQsTopPadding() { 1239 if (mKeyguardShowing 1240 && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) { 1241 1242 // Either QS pushes the notifications down when fully expanded, or QS is fully above the 1243 // notifications (mostly on tablets). maxNotifications denotes the normal top padding 1244 // on Keyguard, maxQs denotes the top padding from the quick settings panel. We need to 1245 // take the maximum and linearly interpolate with the panel expansion for a nice motion. 1246 int maxNotifications = mClockPositionResult.stackScrollerPadding 1247 - mClockPositionResult.stackScrollerPaddingAdjustment; 1248 int maxQs = getTempQsMaxExpansion(); 1249 int max = mStatusBarState == StatusBarState.KEYGUARD 1250 ? Math.max(maxNotifications, maxQs) 1251 : maxQs; 1252 return (int) interpolate(getExpandedFraction(), 1253 mQsMinExpansionHeight, max); 1254 } else if (mQsSizeChangeAnimator != null) { 1255 return (int) mQsSizeChangeAnimator.getAnimatedValue(); 1256 } else if (mKeyguardShowing) { 1257 1258 // We can only do the smoother transition on Keyguard when we also are not collapsing 1259 // from a scrolled quick settings. 1260 return interpolate(getQsExpansionFraction(), 1261 mNotificationStackScroller.getIntrinsicPadding(), 1262 mQsMaxExpansionHeight); 1263 } else { 1264 return mQsExpansionHeight; 1265 } 1266 } 1267 1268 protected void requestScrollerTopPaddingUpdate(boolean animate) { 1269 mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), 1270 mAnimateNextTopPaddingChange || animate, 1271 mKeyguardShowing 1272 && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)); 1273 mAnimateNextTopPaddingChange = false; 1274 } 1275 1276 private void trackMovement(MotionEvent event) { 1277 if (mVelocityTracker != null) mVelocityTracker.addMovement(event); 1278 mLastTouchX = event.getX(); 1279 mLastTouchY = event.getY(); 1280 } 1281 1282 private void initVelocityTracker() { 1283 if (mVelocityTracker != null) { 1284 mVelocityTracker.recycle(); 1285 } 1286 mVelocityTracker = VelocityTracker.obtain(); 1287 } 1288 1289 private float getCurrentVelocity() { 1290 if (mVelocityTracker == null) { 1291 return 0; 1292 } 1293 mVelocityTracker.computeCurrentVelocity(1000); 1294 return mVelocityTracker.getYVelocity(); 1295 } 1296 1297 private void cancelQsAnimation() { 1298 if (mQsExpansionAnimator != null) { 1299 mQsExpansionAnimator.cancel(); 1300 } 1301 } 1302 1303 public void flingSettings(float vel, boolean expand) { 1304 flingSettings(vel, expand, null, false /* isClick */); 1305 } 1306 1307 private void flingSettings(float vel, boolean expand, final Runnable onFinishRunnable, 1308 boolean isClick) { 1309 float target = expand ? mQsMaxExpansionHeight : mQsMinExpansionHeight; 1310 if (target == mQsExpansionHeight) { 1311 if (onFinishRunnable != null) { 1312 onFinishRunnable.run(); 1313 } 1314 return; 1315 } 1316 boolean belowFalsingThreshold = isFalseTouch(); 1317 if (belowFalsingThreshold) { 1318 vel = 0; 1319 } 1320 ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target); 1321 if (isClick) { 1322 animator.setInterpolator(Interpolators.TOUCH_RESPONSE); 1323 animator.setDuration(368); 1324 } else { 1325 mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel); 1326 } 1327 if (belowFalsingThreshold) { 1328 animator.setDuration(350); 1329 } 1330 animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 1331 @Override 1332 public void onAnimationUpdate(ValueAnimator animation) { 1333 setQsExpansion((Float) animation.getAnimatedValue()); 1334 } 1335 }); 1336 animator.addListener(new AnimatorListenerAdapter() { 1337 @Override 1338 public void onAnimationEnd(Animator animation) { 1339 mQsExpansionAnimator = null; 1340 if (onFinishRunnable != null) { 1341 onFinishRunnable.run(); 1342 } 1343 } 1344 }); 1345 animator.start(); 1346 mQsExpansionAnimator = animator; 1347 mQsAnimatorExpand = expand; 1348 } 1349 1350 /** 1351 * @return Whether we should intercept a gesture to open Quick Settings. 1352 */ 1353 private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) { 1354 if (!mQsExpansionEnabled || mCollapsedOnDown) { 1355 return false; 1356 } 1357 View header = mKeyguardShowing ? mKeyguardStatusBar : mQsContainer.getHeader(); 1358 boolean onHeader = x >= mQsAutoReinflateContainer.getX() 1359 && x <= mQsAutoReinflateContainer.getX() + mQsAutoReinflateContainer.getWidth() 1360 && y >= header.getTop() && y <= header.getBottom(); 1361 if (mQsExpanded) { 1362 return onHeader || (yDiff < 0 && isInQsArea(x, y)); 1363 } else { 1364 return onHeader; 1365 } 1366 } 1367 1368 @Override 1369 protected boolean isScrolledToBottom() { 1370 if (!isInSettings()) { 1371 return mStatusBar.getBarState() == StatusBarState.KEYGUARD 1372 || mNotificationStackScroller.isScrolledToBottom(); 1373 } else { 1374 return true; 1375 } 1376 } 1377 1378 @Override 1379 protected int getMaxPanelHeight() { 1380 int min = mStatusBarMinHeight; 1381 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD 1382 && mNotificationStackScroller.getNotGoneChildCount() == 0) { 1383 int minHeight = (int) (mQsMinExpansionHeight + getOverExpansionAmount()); 1384 min = Math.max(min, minHeight); 1385 } 1386 int maxHeight; 1387 if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted) { 1388 maxHeight = calculatePanelHeightQsExpanded(); 1389 } else { 1390 maxHeight = calculatePanelHeightShade(); 1391 } 1392 maxHeight = Math.max(maxHeight, min); 1393 return maxHeight; 1394 } 1395 1396 public boolean isInSettings() { 1397 return mQsExpanded; 1398 } 1399 1400 public boolean isExpanding() { 1401 return mIsExpanding; 1402 } 1403 1404 @Override 1405 protected void onHeightUpdated(float expandedHeight) { 1406 if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) { 1407 positionClockAndNotifications(); 1408 } 1409 if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null 1410 && !mQsExpansionFromOverscroll) { 1411 float t; 1412 if (mKeyguardShowing) { 1413 1414 // On Keyguard, interpolate the QS expansion linearly to the panel expansion 1415 t = expandedHeight / getMaxPanelHeight(); 1416 } else { 1417 1418 // In Shade, interpolate linearly such that QS is closed whenever panel height is 1419 // minimum QS expansion + minStackHeight 1420 float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding() 1421 + mNotificationStackScroller.getLayoutMinHeight(); 1422 float panelHeightQsExpanded = calculatePanelHeightQsExpanded(); 1423 t = (expandedHeight - panelHeightQsCollapsed) 1424 / (panelHeightQsExpanded - panelHeightQsCollapsed); 1425 } 1426 setQsExpansion(mQsMinExpansionHeight 1427 + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight)); 1428 } 1429 updateExpandedHeight(expandedHeight); 1430 updateHeader(); 1431 updateUnlockIcon(); 1432 updateNotificationTranslucency(); 1433 updatePanelExpanded(); 1434 mNotificationStackScroller.setShadeExpanded(!isFullyCollapsed()); 1435 if (DEBUG) { 1436 invalidate(); 1437 } 1438 } 1439 1440 private void updatePanelExpanded() { 1441 boolean isExpanded = !isFullyCollapsed(); 1442 if (mPanelExpanded != isExpanded) { 1443 mHeadsUpManager.setIsExpanded(isExpanded); 1444 mStatusBar.setPanelExpanded(isExpanded); 1445 mPanelExpanded = isExpanded; 1446 } 1447 } 1448 1449 /** 1450 * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when 1451 * collapsing QS / the panel when QS was scrolled 1452 */ 1453 private int getTempQsMaxExpansion() { 1454 return mQsMaxExpansionHeight; 1455 } 1456 1457 private int calculatePanelHeightShade() { 1458 int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin(); 1459 int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin 1460 - mTopPaddingAdjustment; 1461 maxHeight += mNotificationStackScroller.getTopPaddingOverflow(); 1462 return maxHeight; 1463 } 1464 1465 private int calculatePanelHeightQsExpanded() { 1466 float notificationHeight = mNotificationStackScroller.getHeight() 1467 - mNotificationStackScroller.getEmptyBottomMargin() 1468 - mNotificationStackScroller.getTopPadding(); 1469 1470 // When only empty shade view is visible in QS collapsed state, simulate that we would have 1471 // it in expanded QS state as well so we don't run into troubles when fading the view in/out 1472 // and expanding/collapsing the whole panel from/to quick settings. 1473 if (mNotificationStackScroller.getNotGoneChildCount() == 0 1474 && mShadeEmpty) { 1475 notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight() 1476 + mNotificationStackScroller.getBottomStackPeekSize() 1477 + mNotificationStackScroller.getBottomStackSlowDownHeight(); 1478 } 1479 int maxQsHeight = mQsMaxExpansionHeight; 1480 1481 // If an animation is changing the size of the QS panel, take the animated value. 1482 if (mQsSizeChangeAnimator != null) { 1483 maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue(); 1484 } 1485 float totalHeight = Math.max( 1486 maxQsHeight, mStatusBarState == StatusBarState.KEYGUARD 1487 ? mClockPositionResult.stackScrollerPadding - mTopPaddingAdjustment 1488 : 0) 1489 + notificationHeight + mNotificationStackScroller.getTopPaddingOverflow(); 1490 if (totalHeight > mNotificationStackScroller.getHeight()) { 1491 float fullyCollapsedHeight = maxQsHeight 1492 + mNotificationStackScroller.getLayoutMinHeight(); 1493 totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight()); 1494 } 1495 return (int) totalHeight; 1496 } 1497 1498 private void updateNotificationTranslucency() { 1499 float alpha = 1f; 1500 if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) { 1501 alpha = getFadeoutAlpha(); 1502 } 1503 mNotificationStackScroller.setAlpha(alpha); 1504 } 1505 1506 private float getFadeoutAlpha() { 1507 float alpha = (getNotificationsTopY() + mNotificationStackScroller.getFirstItemMinHeight()) 1508 / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize() 1509 - mNotificationStackScroller.getBottomStackSlowDownHeight()); 1510 alpha = Math.max(0, Math.min(alpha, 1)); 1511 alpha = (float) Math.pow(alpha, 0.75); 1512 return alpha; 1513 } 1514 1515 @Override 1516 protected float getOverExpansionAmount() { 1517 return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */); 1518 } 1519 1520 @Override 1521 protected float getOverExpansionPixels() { 1522 return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */); 1523 } 1524 1525 private void updateUnlockIcon() { 1526 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1527 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1528 boolean active = getMaxPanelHeight() - getExpandedHeight() > mUnlockMoveDistance; 1529 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1530 if (active && !mUnlockIconActive && mTracking) { 1531 lockIcon.setImageAlpha(1.0f, true, 150, Interpolators.FAST_OUT_LINEAR_IN, null); 1532 lockIcon.setImageScale(LOCK_ICON_ACTIVE_SCALE, true, 150, 1533 Interpolators.FAST_OUT_LINEAR_IN); 1534 } else if (!active && mUnlockIconActive && mTracking) { 1535 lockIcon.setImageAlpha(lockIcon.getRestingAlpha(), true /* animate */, 1536 150, Interpolators.FAST_OUT_LINEAR_IN, null); 1537 lockIcon.setImageScale(1.0f, true, 150, 1538 Interpolators.FAST_OUT_LINEAR_IN); 1539 } 1540 mUnlockIconActive = active; 1541 } 1542 } 1543 1544 /** 1545 * Hides the header when notifications are colliding with it. 1546 */ 1547 private void updateHeader() { 1548 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1549 updateHeaderKeyguardAlpha(); 1550 } 1551 updateQsExpansion(); 1552 } 1553 1554 protected float getHeaderTranslation() { 1555 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1556 return 0; 1557 } 1558 float translation = NotificationUtils.interpolate(-mQsMinExpansionHeight, 0, 1559 mNotificationStackScroller.getAppearFraction(mExpandedHeight)); 1560 return Math.min(0, translation); 1561 } 1562 1563 /** 1564 * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area) 1565 * during swiping up 1566 */ 1567 private float getKeyguardContentsAlpha() { 1568 float alpha; 1569 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) { 1570 1571 // When on Keyguard, we hide the header as soon as the top card of the notification 1572 // stack scroller is close enough (collision distance) to the bottom of the header. 1573 alpha = getNotificationsTopY() 1574 / 1575 (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance); 1576 } else { 1577 1578 // In SHADE_LOCKED, the top card is already really close to the header. Hide it as 1579 // soon as we start translating the stack. 1580 alpha = getNotificationsTopY() / mKeyguardStatusBar.getHeight(); 1581 } 1582 alpha = MathUtils.constrain(alpha, 0, 1); 1583 alpha = (float) Math.pow(alpha, 0.75); 1584 return alpha; 1585 } 1586 1587 private void updateHeaderKeyguardAlpha() { 1588 float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2); 1589 mKeyguardStatusBar.setAlpha(Math.min(getKeyguardContentsAlpha(), alphaQsExpansion) 1590 * mKeyguardStatusBarAnimateAlpha); 1591 mKeyguardStatusBar.setVisibility(mKeyguardStatusBar.getAlpha() != 0f 1592 && !mDozing ? VISIBLE : INVISIBLE); 1593 } 1594 1595 private void updateKeyguardBottomAreaAlpha() { 1596 float alpha = Math.min(getKeyguardContentsAlpha(), 1 - getQsExpansionFraction()); 1597 mKeyguardBottomArea.setAlpha(alpha); 1598 mKeyguardBottomArea.setImportantForAccessibility(alpha == 0f 1599 ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS 1600 : IMPORTANT_FOR_ACCESSIBILITY_AUTO); 1601 } 1602 1603 private float getNotificationsTopY() { 1604 if (mNotificationStackScroller.getNotGoneChildCount() == 0) { 1605 return getExpandedHeight(); 1606 } 1607 return mNotificationStackScroller.getNotificationsTopY(); 1608 } 1609 1610 @Override 1611 protected void onExpandingStarted() { 1612 super.onExpandingStarted(); 1613 mNotificationStackScroller.onExpansionStarted(); 1614 mIsExpanding = true; 1615 mQsExpandedWhenExpandingStarted = mQsFullyExpanded; 1616 if (mQsExpanded) { 1617 onQsExpansionStarted(); 1618 } 1619 // Since there are QS tiles in the header now, we need to make sure we start listening 1620 // immediately so they can be up to date. 1621 mQsContainer.setHeaderListening(true); 1622 } 1623 1624 @Override 1625 protected void onExpandingFinished() { 1626 super.onExpandingFinished(); 1627 mNotificationStackScroller.onExpansionStopped(); 1628 mHeadsUpManager.onExpandingFinished(); 1629 mIsExpanding = false; 1630 if (isFullyCollapsed()) { 1631 DejankUtils.postAfterTraversal(new Runnable() { 1632 @Override 1633 public void run() { 1634 setListening(false); 1635 } 1636 }); 1637 1638 // Workaround b/22639032: Make sure we invalidate something because else RenderThread 1639 // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go 1640 // ahead with rendering and we jank. 1641 postOnAnimation(new Runnable() { 1642 @Override 1643 public void run() { 1644 getParent().invalidateChild(NotificationPanelView.this, mDummyDirtyRect); 1645 } 1646 }); 1647 } else { 1648 setListening(true); 1649 } 1650 mQsExpandImmediate = false; 1651 mTwoFingerQsExpandPossible = false; 1652 mIsExpansionFromHeadsUp = false; 1653 mNotificationStackScroller.setTrackingHeadsUp(false); 1654 mExpandingFromHeadsUp = false; 1655 setPanelScrimMinFraction(0.0f); 1656 } 1657 1658 private void setListening(boolean listening) { 1659 mQsContainer.setListening(listening); 1660 mKeyguardStatusBar.setListening(listening); 1661 } 1662 1663 @Override 1664 public void expand(boolean animate) { 1665 super.expand(animate); 1666 setListening(true); 1667 } 1668 1669 @Override 1670 protected void setOverExpansion(float overExpansion, boolean isPixels) { 1671 if (mConflictingQsExpansionGesture || mQsExpandImmediate) { 1672 return; 1673 } 1674 if (mStatusBar.getBarState() != StatusBarState.KEYGUARD) { 1675 mNotificationStackScroller.setOnHeightChangedListener(null); 1676 if (isPixels) { 1677 mNotificationStackScroller.setOverScrolledPixels( 1678 overExpansion, true /* onTop */, false /* animate */); 1679 } else { 1680 mNotificationStackScroller.setOverScrollAmount( 1681 overExpansion, true /* onTop */, false /* animate */); 1682 } 1683 mNotificationStackScroller.setOnHeightChangedListener(this); 1684 } 1685 } 1686 1687 @Override 1688 protected void onTrackingStarted() { 1689 mFalsingManager.onTrackingStarted(); 1690 super.onTrackingStarted(); 1691 if (mQsFullyExpanded) { 1692 mQsExpandImmediate = true; 1693 } 1694 if (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1695 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED) { 1696 mAfforanceHelper.animateHideLeftRightIcon(); 1697 } 1698 mNotificationStackScroller.onPanelTrackingStarted(); 1699 } 1700 1701 @Override 1702 protected void onTrackingStopped(boolean expand) { 1703 mFalsingManager.onTrackingStopped(); 1704 super.onTrackingStopped(expand); 1705 if (expand) { 1706 mNotificationStackScroller.setOverScrolledPixels( 1707 0.0f, true /* onTop */, true /* animate */); 1708 } 1709 mNotificationStackScroller.onPanelTrackingStopped(); 1710 if (expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1711 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1712 if (!mHintAnimationRunning) { 1713 mAfforanceHelper.reset(true); 1714 } 1715 } 1716 if (!expand && (mStatusBar.getBarState() == StatusBarState.KEYGUARD 1717 || mStatusBar.getBarState() == StatusBarState.SHADE_LOCKED)) { 1718 KeyguardAffordanceView lockIcon = mKeyguardBottomArea.getLockIcon(); 1719 lockIcon.setImageAlpha(0.0f, true, 100, Interpolators.FAST_OUT_LINEAR_IN, null); 1720 lockIcon.setImageScale(2.0f, true, 100, Interpolators.FAST_OUT_LINEAR_IN); 1721 } 1722 } 1723 1724 @Override 1725 public void onHeightChanged(ExpandableView view, boolean needsAnimation) { 1726 1727 // Block update if we are in quick settings and just the top padding changed 1728 // (i.e. view == null). 1729 if (view == null && mQsExpanded) { 1730 return; 1731 } 1732 ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone(); 1733 ExpandableNotificationRow firstRow = firstChildNotGone instanceof ExpandableNotificationRow 1734 ? (ExpandableNotificationRow) firstChildNotGone 1735 : null; 1736 if (firstRow != null 1737 && (view == firstRow || (firstRow.getNotificationParent() == firstRow))) { 1738 requestScrollerTopPaddingUpdate(false); 1739 } 1740 requestPanelHeightUpdate(); 1741 } 1742 1743 @Override 1744 public void onReset(ExpandableView view) { 1745 } 1746 1747 public void onQsHeightChanged() { 1748 mQsMaxExpansionHeight = mQsContainer.getDesiredHeight(); 1749 if (mQsExpanded && mQsFullyExpanded) { 1750 mQsExpansionHeight = mQsMaxExpansionHeight; 1751 requestScrollerTopPaddingUpdate(false /* animate */); 1752 requestPanelHeightUpdate(); 1753 } 1754 } 1755 1756 @Override 1757 protected void onConfigurationChanged(Configuration newConfig) { 1758 super.onConfigurationChanged(newConfig); 1759 mAfforanceHelper.onConfigurationChanged(); 1760 if (newConfig.orientation != mLastOrientation) { 1761 resetVerticalPanelPosition(); 1762 } 1763 mLastOrientation = newConfig.orientation; 1764 } 1765 1766 @Override 1767 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 1768 mNavigationBarBottomHeight = insets.getStableInsetBottom(); 1769 updateMaxHeadsUpTranslation(); 1770 return insets; 1771 } 1772 1773 private void updateMaxHeadsUpTranslation() { 1774 mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight); 1775 } 1776 1777 @Override 1778 public void onRtlPropertiesChanged(int layoutDirection) { 1779 if (layoutDirection != mOldLayoutDirection) { 1780 mAfforanceHelper.onRtlPropertiesChanged(); 1781 mOldLayoutDirection = layoutDirection; 1782 } 1783 } 1784 1785 @Override 1786 public void onClick(View v) { 1787 if (v.getId() == R.id.expand_indicator) { 1788 onQsExpansionStarted(); 1789 if (mQsExpanded) { 1790 flingSettings(0 /* vel */, false /* expand */, null, true /* isClick */); 1791 } else if (mQsExpansionEnabled) { 1792 EventLogTags.writeSysuiLockscreenGesture( 1793 EventLogConstants.SYSUI_TAP_TO_OPEN_QS, 1794 0, 0); 1795 flingSettings(0 /* vel */, true /* expand */, null, true /* isClick */); 1796 } 1797 } 1798 } 1799 1800 @Override 1801 public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) { 1802 boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage; 1803 mIsLaunchTransitionRunning = true; 1804 mLaunchAnimationEndRunnable = null; 1805 float displayDensity = mStatusBar.getDisplayDensity(); 1806 int lengthDp = Math.abs((int) (translation / displayDensity)); 1807 int velocityDp = Math.abs((int) (vel / displayDensity)); 1808 if (start) { 1809 EventLogTags.writeSysuiLockscreenGesture( 1810 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_DIALER, lengthDp, velocityDp); 1811 1812 mFalsingManager.onLeftAffordanceOn(); 1813 if (mFalsingManager.shouldEnforceBouncer()) { 1814 mStatusBar.executeRunnableDismissingKeyguard(new Runnable() { 1815 @Override 1816 public void run() { 1817 mKeyguardBottomArea.launchLeftAffordance(); 1818 } 1819 }, null, true /* dismissShade */, false /* afterKeyguardGone */, 1820 true /* deferred */); 1821 } 1822 else { 1823 mKeyguardBottomArea.launchLeftAffordance(); 1824 } 1825 } else { 1826 if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals( 1827 mLastCameraLaunchSource)) { 1828 EventLogTags.writeSysuiLockscreenGesture( 1829 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, 1830 lengthDp, velocityDp); 1831 } 1832 mFalsingManager.onCameraOn(); 1833 if (mFalsingManager.shouldEnforceBouncer()) { 1834 mStatusBar.executeRunnableDismissingKeyguard(new Runnable() { 1835 @Override 1836 public void run() { 1837 mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource); 1838 } 1839 }, null, true /* dismissShade */, false /* afterKeyguardGone */, 1840 true /* deferred */); 1841 } 1842 else { 1843 mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource); 1844 } 1845 } 1846 mStatusBar.startLaunchTransitionTimeout(); 1847 mBlockTouches = true; 1848 } 1849 1850 @Override 1851 public void onAnimationToSideEnded() { 1852 mIsLaunchTransitionRunning = false; 1853 mIsLaunchTransitionFinished = true; 1854 if (mLaunchAnimationEndRunnable != null) { 1855 mLaunchAnimationEndRunnable.run(); 1856 mLaunchAnimationEndRunnable = null; 1857 } 1858 } 1859 1860 @Override 1861 protected void startUnlockHintAnimation() { 1862 super.startUnlockHintAnimation(); 1863 startHighlightIconAnimation(getCenterIcon()); 1864 } 1865 1866 /** 1867 * Starts the highlight (making it fully opaque) animation on an icon. 1868 */ 1869 private void startHighlightIconAnimation(final KeyguardAffordanceView icon) { 1870 icon.setImageAlpha(1.0f, true, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1871 Interpolators.FAST_OUT_SLOW_IN, new Runnable() { 1872 @Override 1873 public void run() { 1874 icon.setImageAlpha(icon.getRestingAlpha(), 1875 true /* animate */, KeyguardAffordanceHelper.HINT_PHASE1_DURATION, 1876 Interpolators.FAST_OUT_SLOW_IN, null); 1877 } 1878 }); 1879 } 1880 1881 @Override 1882 public float getMaxTranslationDistance() { 1883 return (float) Math.hypot(getWidth(), getHeight()); 1884 } 1885 1886 @Override 1887 public void onSwipingStarted(boolean rightIcon) { 1888 mFalsingManager.onAffordanceSwipingStarted(rightIcon); 1889 boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon 1890 : rightIcon; 1891 if (camera) { 1892 mKeyguardBottomArea.bindCameraPrewarmService(); 1893 } 1894 requestDisallowInterceptTouchEvent(true); 1895 mOnlyAffordanceInThisMotion = true; 1896 mQsTracking = false; 1897 } 1898 1899 @Override 1900 public void onSwipingAborted() { 1901 mFalsingManager.onAffordanceSwipingAborted(); 1902 mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */); 1903 } 1904 1905 @Override 1906 public void onIconClicked(boolean rightIcon) { 1907 if (mHintAnimationRunning) { 1908 return; 1909 } 1910 mHintAnimationRunning = true; 1911 mAfforanceHelper.startHintAnimation(rightIcon, new Runnable() { 1912 @Override 1913 public void run() { 1914 mHintAnimationRunning = false; 1915 mStatusBar.onHintFinished(); 1916 } 1917 }); 1918 rightIcon = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon; 1919 if (rightIcon) { 1920 mStatusBar.onCameraHintStarted(); 1921 } else { 1922 if (mKeyguardBottomArea.isLeftVoiceAssist()) { 1923 mStatusBar.onVoiceAssistHintStarted(); 1924 } else { 1925 mStatusBar.onPhoneHintStarted(); 1926 } 1927 } 1928 } 1929 1930 @Override 1931 public KeyguardAffordanceView getLeftIcon() { 1932 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1933 ? mKeyguardBottomArea.getRightView() 1934 : mKeyguardBottomArea.getLeftView(); 1935 } 1936 1937 @Override 1938 public KeyguardAffordanceView getCenterIcon() { 1939 return mKeyguardBottomArea.getLockIcon(); 1940 } 1941 1942 @Override 1943 public KeyguardAffordanceView getRightIcon() { 1944 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1945 ? mKeyguardBottomArea.getLeftView() 1946 : mKeyguardBottomArea.getRightView(); 1947 } 1948 1949 @Override 1950 public View getLeftPreview() { 1951 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1952 ? mKeyguardBottomArea.getRightPreview() 1953 : mKeyguardBottomArea.getLeftPreview(); 1954 } 1955 1956 @Override 1957 public View getRightPreview() { 1958 return getLayoutDirection() == LAYOUT_DIRECTION_RTL 1959 ? mKeyguardBottomArea.getLeftPreview() 1960 : mKeyguardBottomArea.getRightPreview(); 1961 } 1962 1963 @Override 1964 public float getAffordanceFalsingFactor() { 1965 return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f; 1966 } 1967 1968 @Override 1969 public boolean needsAntiFalsing() { 1970 return mStatusBarState == StatusBarState.KEYGUARD; 1971 } 1972 1973 @Override 1974 protected float getPeekHeight() { 1975 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 1976 return mNotificationStackScroller.getPeekHeight(); 1977 } else { 1978 return mQsMinExpansionHeight; 1979 } 1980 } 1981 1982 @Override 1983 protected float getCannedFlingDurationFactor() { 1984 if (mQsExpanded) { 1985 return 0.7f; 1986 } else { 1987 return 0.6f; 1988 } 1989 } 1990 1991 @Override 1992 protected boolean fullyExpandedClearAllVisible() { 1993 return mNotificationStackScroller.isDismissViewNotGone() 1994 && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate; 1995 } 1996 1997 @Override 1998 protected boolean isClearAllVisible() { 1999 return mNotificationStackScroller.isDismissViewVisible(); 2000 } 2001 2002 @Override 2003 protected int getClearAllHeight() { 2004 return mNotificationStackScroller.getDismissViewHeight(); 2005 } 2006 2007 @Override 2008 protected boolean isTrackingBlocked() { 2009 return mConflictingQsExpansionGesture && mQsExpanded; 2010 } 2011 2012 public boolean isQsExpanded() { 2013 return mQsExpanded; 2014 } 2015 2016 public boolean isQsDetailShowing() { 2017 return mQsContainer.isShowingDetail(); 2018 } 2019 2020 public void closeQsDetail() { 2021 mQsContainer.getQsPanel().closeDetail(); 2022 } 2023 2024 @Override 2025 public boolean shouldDelayChildPressedState() { 2026 return true; 2027 } 2028 2029 public boolean isLaunchTransitionFinished() { 2030 return mIsLaunchTransitionFinished; 2031 } 2032 2033 public boolean isLaunchTransitionRunning() { 2034 return mIsLaunchTransitionRunning; 2035 } 2036 2037 public void setLaunchTransitionEndRunnable(Runnable r) { 2038 mLaunchAnimationEndRunnable = r; 2039 } 2040 2041 public void setEmptyDragAmount(float amount) { 2042 float factor = 0.8f; 2043 if (mNotificationStackScroller.getNotGoneChildCount() > 0) { 2044 factor = 0.4f; 2045 } else if (!mStatusBar.hasActiveNotifications()) { 2046 factor = 0.4f; 2047 } 2048 mEmptyDragAmount = amount * factor; 2049 positionClockAndNotifications(); 2050 } 2051 2052 private static float interpolate(float t, float start, float end) { 2053 return (1 - t) * start + t * end; 2054 } 2055 2056 public void setDozing(boolean dozing, boolean animate) { 2057 if (dozing == mDozing) return; 2058 mDozing = dozing; 2059 if (mStatusBarState == StatusBarState.KEYGUARD) { 2060 updateDozingVisibilities(animate); 2061 } 2062 } 2063 2064 private void updateDozingVisibilities(boolean animate) { 2065 if (mDozing) { 2066 mKeyguardStatusBar.setVisibility(View.INVISIBLE); 2067 mKeyguardBottomArea.setVisibility(View.INVISIBLE); 2068 } else { 2069 mKeyguardBottomArea.setVisibility(View.VISIBLE); 2070 mKeyguardStatusBar.setVisibility(View.VISIBLE); 2071 if (animate) { 2072 animateKeyguardStatusBarIn(DOZE_ANIMATION_DURATION); 2073 mKeyguardBottomArea.startFinishDozeAnimation(); 2074 } 2075 } 2076 } 2077 2078 @Override 2079 public boolean isDozing() { 2080 return mDozing; 2081 } 2082 2083 public void setShadeEmpty(boolean shadeEmpty) { 2084 mShadeEmpty = shadeEmpty; 2085 updateEmptyShadeView(); 2086 } 2087 2088 private void updateEmptyShadeView() { 2089 2090 // Hide "No notifications" in QS. 2091 mNotificationStackScroller.updateEmptyShadeView(mShadeEmpty && !mQsExpanded); 2092 } 2093 2094 public void setQsScrimEnabled(boolean qsScrimEnabled) { 2095 boolean changed = mQsScrimEnabled != qsScrimEnabled; 2096 mQsScrimEnabled = qsScrimEnabled; 2097 if (changed) { 2098 updateQsState(); 2099 } 2100 } 2101 2102 public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) { 2103 mKeyguardUserSwitcher = keyguardUserSwitcher; 2104 } 2105 2106 private final Runnable mUpdateHeader = new Runnable() { 2107 @Override 2108 public void run() { 2109 mQsContainer.getHeader().updateEverything(); 2110 } 2111 }; 2112 2113 public void onScreenTurningOn() { 2114 mKeyguardStatusView.refreshTime(); 2115 } 2116 2117 @Override 2118 public void onEmptySpaceClicked(float x, float y) { 2119 onEmptySpaceClick(x); 2120 } 2121 2122 @Override 2123 protected boolean onMiddleClicked() { 2124 switch (mStatusBar.getBarState()) { 2125 case StatusBarState.KEYGUARD: 2126 if (!mDozingOnDown) { 2127 EventLogTags.writeSysuiLockscreenGesture( 2128 EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_TAP_UNLOCK_HINT, 2129 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */); 2130 startUnlockHintAnimation(); 2131 } 2132 return true; 2133 case StatusBarState.SHADE_LOCKED: 2134 if (!mQsExpanded) { 2135 mStatusBar.goToKeyguard(); 2136 } 2137 return true; 2138 case StatusBarState.SHADE: 2139 2140 // This gets called in the middle of the touch handling, where the state is still 2141 // that we are tracking the panel. Collapse the panel after this is done. 2142 post(mPostCollapseRunnable); 2143 return false; 2144 default: 2145 return true; 2146 } 2147 } 2148 2149 @Override 2150 protected void dispatchDraw(Canvas canvas) { 2151 super.dispatchDraw(canvas); 2152 if (DEBUG) { 2153 Paint p = new Paint(); 2154 p.setColor(Color.RED); 2155 p.setStrokeWidth(2); 2156 p.setStyle(Paint.Style.STROKE); 2157 canvas.drawLine(0, getMaxPanelHeight(), getWidth(), getMaxPanelHeight(), p); 2158 p.setColor(Color.BLUE); 2159 canvas.drawLine(0, getExpandedHeight(), getWidth(), getExpandedHeight(), p); 2160 p.setColor(Color.GREEN); 2161 canvas.drawLine(0, calculatePanelHeightQsExpanded(), getWidth(), 2162 calculatePanelHeightQsExpanded(), p); 2163 p.setColor(Color.YELLOW); 2164 canvas.drawLine(0, calculatePanelHeightShade(), getWidth(), 2165 calculatePanelHeightShade(), p); 2166 p.setColor(Color.MAGENTA); 2167 canvas.drawLine(0, calculateQsTopPadding(), getWidth(), 2168 calculateQsTopPadding(), p); 2169 p.setColor(Color.CYAN); 2170 canvas.drawLine(0, mNotificationStackScroller.getTopPadding(), getWidth(), 2171 mNotificationStackScroller.getTopPadding(), p); 2172 } 2173 } 2174 2175 @Override 2176 public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) { 2177 if (inPinnedMode) { 2178 mHeadsUpExistenceChangedRunnable.run(); 2179 updateNotificationTranslucency(); 2180 } else { 2181 mHeadsUpAnimatingAway = true; 2182 mNotificationStackScroller.runAfterAnimationFinished( 2183 mHeadsUpExistenceChangedRunnable); 2184 } 2185 } 2186 2187 @Override 2188 public void onHeadsUpPinned(ExpandableNotificationRow headsUp) { 2189 mNotificationStackScroller.generateHeadsUpAnimation(headsUp, true); 2190 } 2191 2192 @Override 2193 public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) { 2194 } 2195 2196 @Override 2197 public void onHeadsUpStateChanged(NotificationData.Entry entry, boolean isHeadsUp) { 2198 mNotificationStackScroller.generateHeadsUpAnimation(entry.row, isHeadsUp); 2199 } 2200 2201 @Override 2202 public void setHeadsUpManager(HeadsUpManager headsUpManager) { 2203 super.setHeadsUpManager(headsUpManager); 2204 mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager, mNotificationStackScroller, 2205 this); 2206 } 2207 2208 public void setTrackingHeadsUp(boolean tracking) { 2209 if (tracking) { 2210 mNotificationStackScroller.setTrackingHeadsUp(true); 2211 mExpandingFromHeadsUp = true; 2212 } 2213 // otherwise we update the state when the expansion is finished 2214 } 2215 2216 @Override 2217 protected void onClosingFinished() { 2218 super.onClosingFinished(); 2219 resetVerticalPanelPosition(); 2220 setClosingWithAlphaFadeout(false); 2221 } 2222 2223 private void setClosingWithAlphaFadeout(boolean closing) { 2224 mClosingWithAlphaFadeOut = closing; 2225 mNotificationStackScroller.forceNoOverlappingRendering(closing); 2226 } 2227 2228 /** 2229 * Updates the vertical position of the panel so it is positioned closer to the touch 2230 * responsible for opening the panel. 2231 * 2232 * @param x the x-coordinate the touch event 2233 */ 2234 protected void updateVerticalPanelPosition(float x) { 2235 if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) { 2236 resetVerticalPanelPosition(); 2237 return; 2238 } 2239 float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2; 2240 float rightMost = getWidth() - mPositionMinSideMargin 2241 - mNotificationStackScroller.getWidth() / 2; 2242 if (Math.abs(x - getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) { 2243 x = getWidth() / 2; 2244 } 2245 x = Math.min(rightMost, Math.max(leftMost, x)); 2246 setVerticalPanelTranslation(x - 2247 (mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2)); 2248 } 2249 2250 private void resetVerticalPanelPosition() { 2251 setVerticalPanelTranslation(0f); 2252 } 2253 2254 protected void setVerticalPanelTranslation(float translation) { 2255 mNotificationStackScroller.setTranslationX(translation); 2256 mQsAutoReinflateContainer.setTranslationX(translation); 2257 } 2258 2259 protected void updateExpandedHeight(float expandedHeight) { 2260 mNotificationStackScroller.setExpandedHeight(expandedHeight); 2261 updateKeyguardBottomAreaAlpha(); 2262 } 2263 2264 public void setPanelScrimMinFraction(float minFraction) { 2265 mBar.panelScrimMinFractionChanged(minFraction); 2266 } 2267 2268 public void clearNotificationEffects() { 2269 mStatusBar.clearNotificationEffects(); 2270 } 2271 2272 @Override 2273 protected boolean isPanelVisibleBecauseOfHeadsUp() { 2274 return mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway; 2275 } 2276 2277 @Override 2278 public boolean hasOverlappingRendering() { 2279 return !mDozing; 2280 } 2281 2282 public void launchCamera(boolean animate, int source) { 2283 if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) { 2284 mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP; 2285 } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) { 2286 mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE; 2287 } else { 2288 2289 // Default. 2290 mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE; 2291 } 2292 2293 // If we are launching it when we are occluded already we don't want it to animate, 2294 // nor setting these flags, since the occluded state doesn't change anymore, hence it's 2295 // never reset. 2296 if (!isFullyCollapsed()) { 2297 mLaunchingAffordance = true; 2298 setLaunchingAffordance(true); 2299 } else { 2300 animate = false; 2301 } 2302 mAfforanceHelper.launchAffordance(animate, getLayoutDirection() == LAYOUT_DIRECTION_RTL); 2303 } 2304 2305 public void onAffordanceLaunchEnded() { 2306 mLaunchingAffordance = false; 2307 setLaunchingAffordance(false); 2308 } 2309 2310 @Override 2311 public void setAlpha(float alpha) { 2312 super.setAlpha(alpha); 2313 mNotificationStackScroller.setParentFadingOut(alpha != 1.0f); 2314 } 2315 2316 /** 2317 * Set whether we are currently launching an affordance. This is currently only set when 2318 * launched via a camera gesture. 2319 */ 2320 private void setLaunchingAffordance(boolean launchingAffordance) { 2321 getLeftIcon().setLaunchingAffordance(launchingAffordance); 2322 getRightIcon().setLaunchingAffordance(launchingAffordance); 2323 getCenterIcon().setLaunchingAffordance(launchingAffordance); 2324 } 2325 2326 /** 2327 * Whether the camera application can be launched for the camera launch gesture. 2328 * 2329 * @param keyguardIsShowing whether keyguard is being shown 2330 */ 2331 public boolean canCameraGestureBeLaunched(boolean keyguardIsShowing) { 2332 ResolveInfo resolveInfo = mKeyguardBottomArea.resolveCameraIntent(); 2333 String packageToLaunch = (resolveInfo == null || resolveInfo.activityInfo == null) 2334 ? null : resolveInfo.activityInfo.packageName; 2335 return packageToLaunch != null && 2336 (keyguardIsShowing || !isForegroundApp(packageToLaunch)) && 2337 !mAfforanceHelper.isSwipingInProgress(); 2338 } 2339 2340 /** 2341 * Return true if the applications with the package name is running in foreground. 2342 * 2343 * @param pkgName application package name. 2344 */ 2345 private boolean isForegroundApp(String pkgName) { 2346 ActivityManager am = getContext().getSystemService(ActivityManager.class); 2347 List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1); 2348 return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName()); 2349 } 2350 2351 public void setGroupManager(NotificationGroupManager groupManager) { 2352 mGroupManager = groupManager; 2353 } 2354 } 2355