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