KeyguardWidgetPager.java revision 5ecd81154fa039961f65bb4e36d18ac555b0d1d6
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 */ 16package com.android.keyguard; 17 18import android.animation.Animator; 19import android.animation.AnimatorListenerAdapter; 20import android.animation.AnimatorSet; 21import android.animation.ObjectAnimator; 22import android.animation.PropertyValuesHolder; 23import android.animation.TimeInterpolator; 24import android.appwidget.AppWidgetHostView; 25import android.appwidget.AppWidgetManager; 26import android.appwidget.AppWidgetProviderInfo; 27import android.content.Context; 28import android.os.Handler; 29import android.os.HandlerThread; 30import android.text.format.DateFormat; 31import android.util.AttributeSet; 32import android.util.Slog; 33import android.view.Gravity; 34import android.view.MotionEvent; 35import android.view.View; 36import android.view.View.OnLongClickListener; 37import android.view.ViewGroup; 38import android.view.accessibility.AccessibilityEvent; 39import android.view.accessibility.AccessibilityManager; 40import android.view.animation.DecelerateInterpolator; 41import android.widget.FrameLayout; 42import android.widget.TextClock; 43 44import com.android.internal.widget.LockPatternUtils; 45 46import java.util.ArrayList; 47import java.util.TimeZone; 48 49public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwitchListener, 50 OnLongClickListener, ChallengeLayout.OnBouncerStateChangedListener { 51 52 ZInterpolator mZInterpolator = new ZInterpolator(0.5f); 53 private static float CAMERA_DISTANCE = 10000; 54 protected static float OVERSCROLL_MAX_ROTATION = 30; 55 private static final boolean PERFORM_OVERSCROLL_ROTATION = true; 56 57 private static final int FLAG_HAS_LOCAL_HOUR = 0x1; 58 private static final int FLAG_HAS_LOCAL_MINUTE = 0x2; 59 60 protected KeyguardViewStateManager mViewStateManager; 61 private LockPatternUtils mLockPatternUtils; 62 63 // Related to the fading in / out background outlines 64 public static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375; 65 public static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100; 66 protected AnimatorSet mChildrenOutlineFadeAnimation; 67 protected int mScreenCenter; 68 private boolean mHasMeasure = false; 69 boolean showHintsAfterLayout = false; 70 71 private static final long CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT = 30000; 72 private static final String TAG = "KeyguardWidgetPager"; 73 private boolean mCenterSmallWidgetsVertically; 74 75 private int mPage = 0; 76 private Callbacks mCallbacks; 77 78 private int mWidgetToResetAfterFadeOut; 79 protected boolean mShowingInitialHints = false; 80 81 // A temporary handle to the Add-Widget view 82 private View mAddWidgetView; 83 private int mLastWidthMeasureSpec; 84 private int mLastHeightMeasureSpec; 85 86 // Bouncer 87 private int mBouncerZoomInOutDuration = 250; 88 private float BOUNCER_SCALE_FACTOR = 0.67f; 89 90 // Background worker thread: used here for persistence, also made available to widget frames 91 private final HandlerThread mBackgroundWorkerThread; 92 private final Handler mBackgroundWorkerHandler; 93 94 public KeyguardWidgetPager(Context context, AttributeSet attrs) { 95 this(context, attrs, 0); 96 } 97 98 public KeyguardWidgetPager(Context context) { 99 this(null, null, 0); 100 } 101 102 public KeyguardWidgetPager(Context context, AttributeSet attrs, int defStyle) { 103 super(context, attrs, defStyle); 104 if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 105 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 106 } 107 108 setPageSwitchListener(this); 109 110 mBackgroundWorkerThread = new HandlerThread("KeyguardWidgetPager Worker"); 111 mBackgroundWorkerThread.start(); 112 mBackgroundWorkerHandler = new Handler(mBackgroundWorkerThread.getLooper()); 113 } 114 115 @Override 116 protected void onDetachedFromWindow() { 117 super.onDetachedFromWindow(); 118 119 // Clean up the worker thread 120 mBackgroundWorkerThread.quit(); 121 } 122 123 public void setViewStateManager(KeyguardViewStateManager viewStateManager) { 124 mViewStateManager = viewStateManager; 125 } 126 127 public void setLockPatternUtils(LockPatternUtils l) { 128 mLockPatternUtils = l; 129 } 130 131 @Override 132 public void onPageSwitching(View newPage, int newPageIndex) { 133 if (mViewStateManager != null) { 134 mViewStateManager.onPageSwitching(newPage, newPageIndex); 135 } 136 } 137 138 @Override 139 public void onPageSwitched(View newPage, int newPageIndex) { 140 boolean showingClock = false; 141 if (newPage instanceof ViewGroup) { 142 ViewGroup vg = (ViewGroup) newPage; 143 if (vg.getChildAt(0) instanceof KeyguardStatusView) { 144 showingClock = true; 145 } 146 } 147 148 if (newPage != null && 149 findClockInHierarchy(newPage) == (FLAG_HAS_LOCAL_HOUR | FLAG_HAS_LOCAL_MINUTE)) { 150 showingClock = true; 151 } 152 153 // Disable the status bar clock if we're showing the default status widget 154 if (showingClock) { 155 setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_CLOCK); 156 } else { 157 setSystemUiVisibility(getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_CLOCK); 158 } 159 160 // Extend the display timeout if the user switches pages 161 if (mPage != newPageIndex) { 162 int oldPageIndex = mPage; 163 mPage = newPageIndex; 164 userActivity(); 165 KeyguardWidgetFrame oldWidgetPage = getWidgetPageAt(oldPageIndex); 166 if (oldWidgetPage != null) { 167 oldWidgetPage.onActive(false); 168 } 169 KeyguardWidgetFrame newWidgetPage = getWidgetPageAt(newPageIndex); 170 if (newWidgetPage != null) { 171 newWidgetPage.onActive(true); 172 newWidgetPage.requestAccessibilityFocus(); 173 } 174 if (mParent != null && AccessibilityManager.getInstance(mContext).isEnabled()) { 175 AccessibilityEvent event = AccessibilityEvent.obtain( 176 AccessibilityEvent.TYPE_VIEW_SCROLLED); 177 onInitializeAccessibilityEvent(event); 178 onPopulateAccessibilityEvent(event); 179 mParent.requestSendAccessibilityEvent(this, event); 180 } 181 } 182 if (mViewStateManager != null) { 183 mViewStateManager.onPageSwitched(newPage, newPageIndex); 184 } 185 } 186 187 @Override 188 public void sendAccessibilityEvent(int eventType) { 189 if (eventType != AccessibilityEvent.TYPE_VIEW_SCROLLED || isPageMoving()) { 190 super.sendAccessibilityEvent(eventType); 191 } 192 } 193 194 private void updateWidgetFramesImportantForAccessibility() { 195 final int pageCount = getPageCount(); 196 for (int i = 0; i < pageCount; i++) { 197 KeyguardWidgetFrame frame = getWidgetPageAt(i); 198 updateWidgetFrameImportantForAccessibility(frame); 199 } 200 } 201 202 private void updateWidgetFrameImportantForAccessibility(KeyguardWidgetFrame frame) { 203 if (frame.getContentAlpha() <= 0) { 204 frame.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); 205 } else { 206 frame.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 207 } 208 } 209 210 private void userActivity() { 211 if (mCallbacks != null) { 212 mCallbacks.onUserActivityTimeoutChanged(); 213 mCallbacks.userActivity(); 214 } 215 } 216 217 @Override 218 public boolean onTouchEvent(MotionEvent ev) { 219 return captureUserInteraction(ev) || super.onTouchEvent(ev); 220 } 221 222 @Override 223 public boolean onInterceptTouchEvent(MotionEvent ev) { 224 return captureUserInteraction(ev) || super.onInterceptTouchEvent(ev); 225 } 226 227 private boolean captureUserInteraction(MotionEvent ev) { 228 KeyguardWidgetFrame currentWidgetPage = getWidgetPageAt(getCurrentPage()); 229 return currentWidgetPage != null && currentWidgetPage.onUserInteraction(ev); 230 } 231 232 public void showPagingFeedback() { 233 // Nothing yet. 234 } 235 236 public long getUserActivityTimeout() { 237 View page = getPageAt(mPage); 238 if (page instanceof ViewGroup) { 239 ViewGroup vg = (ViewGroup) page; 240 View view = vg.getChildAt(0); 241 if (!(view instanceof KeyguardStatusView) 242 && !(view instanceof KeyguardMultiUserSelectorView)) { 243 return CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT; 244 } 245 } 246 return -1; 247 } 248 249 public void setCallbacks(Callbacks callbacks) { 250 mCallbacks = callbacks; 251 } 252 253 public interface Callbacks { 254 public void userActivity(); 255 public void onUserActivityTimeoutChanged(); 256 public void onAddView(View v); 257 public void onRemoveView(View v, boolean deletePermanently); 258 public void onRemoveViewAnimationCompleted(); 259 } 260 261 public void addWidget(View widget) { 262 addWidget(widget, -1); 263 } 264 265 public void onRemoveView(View v, final boolean deletePermanently) { 266 final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); 267 if (mCallbacks != null) { 268 mCallbacks.onRemoveView(v, deletePermanently); 269 } 270 mBackgroundWorkerHandler.post(new Runnable() { 271 @Override 272 public void run() { 273 mLockPatternUtils.removeAppWidget(appWidgetId); 274 } 275 }); 276 } 277 278 @Override 279 public void onRemoveViewAnimationCompleted() { 280 if (mCallbacks != null) { 281 mCallbacks.onRemoveViewAnimationCompleted(); 282 } 283 } 284 285 public void onAddView(View v, final int index) { 286 final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); 287 final int[] pagesRange = new int[mTempVisiblePagesRange.length]; 288 getVisiblePages(pagesRange); 289 boundByReorderablePages(true, pagesRange); 290 if (mCallbacks != null) { 291 mCallbacks.onAddView(v); 292 } 293 // Subtract from the index to take into account pages before the reorderable 294 // pages (e.g. the "add widget" page) 295 mBackgroundWorkerHandler.post(new Runnable() { 296 @Override 297 public void run() { 298 mLockPatternUtils.addAppWidget(appWidgetId, index - pagesRange[0]); 299 } 300 }); 301 } 302 303 /* 304 * We wrap widgets in a special frame which handles drawing the over scroll foreground. 305 */ 306 public void addWidget(View widget, int pageIndex) { 307 KeyguardWidgetFrame frame; 308 // All views contained herein should be wrapped in a KeyguardWidgetFrame 309 if (!(widget instanceof KeyguardWidgetFrame)) { 310 frame = new KeyguardWidgetFrame(getContext()); 311 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, 312 LayoutParams.MATCH_PARENT); 313 lp.gravity = Gravity.TOP; 314 315 // The framework adds a default padding to AppWidgetHostView. We don't need this padding 316 // for the Keyguard, so we override it to be 0. 317 widget.setPadding(0, 0, 0, 0); 318 frame.addView(widget, lp); 319 320 // We set whether or not this widget supports vertical resizing. 321 if (widget instanceof AppWidgetHostView) { 322 AppWidgetHostView awhv = (AppWidgetHostView) widget; 323 AppWidgetProviderInfo info = awhv.getAppWidgetInfo(); 324 if ((info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) { 325 frame.setWidgetLockedSmall(false); 326 } else { 327 // Lock the widget to be small. 328 frame.setWidgetLockedSmall(true); 329 if (mCenterSmallWidgetsVertically) { 330 lp.gravity = Gravity.CENTER; 331 } 332 } 333 } 334 } else { 335 frame = (KeyguardWidgetFrame) widget; 336 } 337 338 ViewGroup.LayoutParams pageLp = new ViewGroup.LayoutParams( 339 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 340 frame.setOnLongClickListener(this); 341 frame.setWorkerHandler(mBackgroundWorkerHandler); 342 343 if (pageIndex == -1) { 344 addView(frame, pageLp); 345 } else { 346 addView(frame, pageIndex, pageLp); 347 } 348 349 // Update the frame content description. 350 View content = (widget == frame) ? frame.getContent() : widget; 351 if (content != null) { 352 String contentDescription = mContext.getString( 353 R.string.keyguard_accessibility_widget, 354 content.getContentDescription()); 355 frame.setContentDescription(contentDescription); 356 } 357 updateWidgetFrameImportantForAccessibility(frame); 358 } 359 360 /** 361 * Use addWidget() instead. 362 * @deprecated 363 */ 364 @Override 365 public void addView(View child, int index) { 366 enforceKeyguardWidgetFrame(child); 367 super.addView(child, index); 368 } 369 370 /** 371 * Use addWidget() instead. 372 * @deprecated 373 */ 374 @Override 375 public void addView(View child, int width, int height) { 376 enforceKeyguardWidgetFrame(child); 377 super.addView(child, width, height); 378 } 379 380 /** 381 * Use addWidget() instead. 382 * @deprecated 383 */ 384 @Override 385 public void addView(View child, LayoutParams params) { 386 enforceKeyguardWidgetFrame(child); 387 super.addView(child, params); 388 } 389 390 /** 391 * Use addWidget() instead. 392 * @deprecated 393 */ 394 @Override 395 public void addView(View child, int index, LayoutParams params) { 396 enforceKeyguardWidgetFrame(child); 397 super.addView(child, index, params); 398 } 399 400 private void enforceKeyguardWidgetFrame(View child) { 401 if (!(child instanceof KeyguardWidgetFrame)) { 402 throw new IllegalArgumentException( 403 "KeyguardWidgetPager children must be KeyguardWidgetFrames"); 404 } 405 } 406 407 public KeyguardWidgetFrame getWidgetPageAt(int index) { 408 // This is always a valid cast as we've guarded the ability to 409 return (KeyguardWidgetFrame) getChildAt(index); 410 } 411 412 protected void onUnhandledTap(MotionEvent ev) { 413 showPagingFeedback(); 414 } 415 416 @Override 417 protected void onPageBeginMoving() { 418 if (mViewStateManager != null) { 419 mViewStateManager.onPageBeginMoving(); 420 } 421 if (!isReordering(false)) { 422 showOutlinesAndSidePages(); 423 } 424 userActivity(); 425 } 426 427 @Override 428 protected void onPageEndMoving() { 429 if (mViewStateManager != null) { 430 mViewStateManager.onPageEndMoving(); 431 } 432 433 // In the reordering case, the pages will be faded appropriately on completion 434 // of the zoom in animation. 435 if (!isReordering(false)) { 436 hideOutlinesAndSidePages(); 437 } 438 } 439 440 protected void enablePageContentLayers() { 441 int children = getChildCount(); 442 for (int i = 0; i < children; i++) { 443 getWidgetPageAt(i).enableHardwareLayersForContent(); 444 } 445 } 446 447 protected void disablePageContentLayers() { 448 int children = getChildCount(); 449 for (int i = 0; i < children; i++) { 450 getWidgetPageAt(i).disableHardwareLayersForContent(); 451 } 452 } 453 454 /* 455 * This interpolator emulates the rate at which the perceived scale of an object changes 456 * as its distance from a camera increases. When this interpolator is applied to a scale 457 * animation on a view, it evokes the sense that the object is shrinking due to moving away 458 * from the camera. 459 */ 460 static class ZInterpolator implements TimeInterpolator { 461 private float focalLength; 462 463 public ZInterpolator(float foc) { 464 focalLength = foc; 465 } 466 467 public float getInterpolation(float input) { 468 return (1.0f - focalLength / (focalLength + input)) / 469 (1.0f - focalLength / (focalLength + 1.0f)); 470 } 471 } 472 473 @Override 474 protected void overScroll(float amount) { 475 acceleratedOverScroll(amount); 476 } 477 478 float backgroundAlphaInterpolator(float r) { 479 return Math.min(1f, r); 480 } 481 482 private void updatePageAlphaValues(int screenCenter) { 483 } 484 485 public float getAlphaForPage(int screenCenter, int index, boolean showSidePages) { 486 if (showSidePages) { 487 return 1f; 488 } else { 489 return index == mCurrentPage ? 1.0f : 0f; 490 } 491 } 492 493 public float getOutlineAlphaForPage(int screenCenter, int index, boolean showSidePages) { 494 if (showSidePages) { 495 return getAlphaForPage(screenCenter, index, showSidePages) 496 * KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER; 497 } else { 498 return 0f; 499 } 500 } 501 502 protected boolean isOverScrollChild(int index, float scrollProgress) { 503 boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX; 504 return (isInOverscroll && (index == 0 && scrollProgress < 0 || 505 index == getChildCount() - 1 && scrollProgress > 0)); 506 } 507 508 @Override 509 protected void screenScrolled(int screenCenter) { 510 mScreenCenter = screenCenter; 511 updatePageAlphaValues(screenCenter); 512 for (int i = 0; i < getChildCount(); i++) { 513 KeyguardWidgetFrame v = getWidgetPageAt(i); 514 if (v == mDragView) continue; 515 if (v != null) { 516 float scrollProgress = getScrollProgress(screenCenter, v, i); 517 518 v.setCameraDistance(mDensity * CAMERA_DISTANCE); 519 520 if (isOverScrollChild(i, scrollProgress) && PERFORM_OVERSCROLL_ROTATION) { 521 float pivotX = v.getMeasuredWidth() / 2; 522 float pivotY = v.getMeasuredHeight() / 2; 523 v.setPivotX(pivotX); 524 v.setPivotY(pivotY); 525 v.setRotationY(- OVERSCROLL_MAX_ROTATION * scrollProgress); 526 v.setOverScrollAmount(Math.abs(scrollProgress), scrollProgress < 0); 527 } else { 528 v.setRotationY(0f); 529 v.setOverScrollAmount(0, false); 530 } 531 532 float alpha = v.getAlpha(); 533 // If the view has 0 alpha, we set it to be invisible so as to prevent 534 // it from accepting touches 535 if (alpha == 0) { 536 v.setVisibility(INVISIBLE); 537 } else if (v.getVisibility() != VISIBLE) { 538 v.setVisibility(VISIBLE); 539 } 540 } 541 } 542 } 543 544 public boolean isWidgetPage(int pageIndex) { 545 if (pageIndex < 0 || pageIndex >= getChildCount()) { 546 return false; 547 } 548 View v = getChildAt(pageIndex); 549 if (v != null && v instanceof KeyguardWidgetFrame) { 550 KeyguardWidgetFrame kwf = (KeyguardWidgetFrame) v; 551 return kwf.getContentAppWidgetId() != AppWidgetManager.INVALID_APPWIDGET_ID; 552 } 553 return false; 554 } 555 556 /** 557 * Returns the bounded set of pages that are re-orderable. The range is fully inclusive. 558 */ 559 @Override 560 void boundByReorderablePages(boolean isReordering, int[] range) { 561 if (isReordering) { 562 // Remove non-widget pages from the range 563 while (range[1] >= range[0] && !isWidgetPage(range[1])) { 564 range[1]--; 565 } 566 while (range[0] <= range[1] && !isWidgetPage(range[0])) { 567 range[0]++; 568 } 569 } 570 } 571 572 protected void reorderStarting() { 573 showOutlinesAndSidePages(); 574 } 575 576 @Override 577 protected void onStartReordering() { 578 super.onStartReordering(); 579 enablePageContentLayers(); 580 reorderStarting(); 581 } 582 583 @Override 584 protected void onEndReordering() { 585 super.onEndReordering(); 586 hideOutlinesAndSidePages(); 587 } 588 589 void showOutlinesAndSidePages() { 590 animateOutlinesAndSidePages(true); 591 } 592 593 void hideOutlinesAndSidePages() { 594 animateOutlinesAndSidePages(false); 595 } 596 597 public void showInitialPageHints() { 598 mShowingInitialHints = true; 599 int count = getChildCount(); 600 for (int i = 0; i < count; i++) { 601 KeyguardWidgetFrame child = getWidgetPageAt(i); 602 if (i != mCurrentPage) { 603 child.setBackgroundAlpha(KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER); 604 child.setContentAlpha(0f); 605 } else { 606 child.setBackgroundAlpha(0f); 607 child.setContentAlpha(1f); 608 } 609 } 610 } 611 612 @Override 613 void setCurrentPage(int currentPage) { 614 super.setCurrentPage(currentPage); 615 updateWidgetFramesImportantForAccessibility(); 616 } 617 618 @Override 619 public void onAttachedToWindow() { 620 super.onAttachedToWindow(); 621 mHasMeasure = false; 622 } 623 624 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 625 mLastWidthMeasureSpec = widthMeasureSpec; 626 mLastHeightMeasureSpec = heightMeasureSpec; 627 628 int maxChallengeTop = -1; 629 View parent = (View) getParent(); 630 boolean challengeShowing = false; 631 // Widget pages need to know where the top of the sliding challenge is so that they 632 // now how big the widget should be when the challenge is up. We compute it here and 633 // then propagate it to each of our children. 634 if (parent.getParent() instanceof SlidingChallengeLayout) { 635 SlidingChallengeLayout scl = (SlidingChallengeLayout) parent.getParent(); 636 int top = scl.getMaxChallengeTop(); 637 638 // This is a bit evil, but we need to map a coordinate relative to the SCL into a 639 // coordinate relative to our children, hence we subtract the top padding.s 640 maxChallengeTop = top - getPaddingTop(); 641 challengeShowing = scl.isChallengeShowing(); 642 643 int count = getChildCount(); 644 for (int i = 0; i < count; i++) { 645 KeyguardWidgetFrame frame = getWidgetPageAt(i); 646 frame.setMaxChallengeTop(maxChallengeTop); 647 // On the very first measure pass, if the challenge is showing, we need to make sure 648 // that the widget on the current page is small. 649 if (challengeShowing && i == mCurrentPage && !mHasMeasure) { 650 frame.shrinkWidget(); 651 } 652 } 653 } 654 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 655 mHasMeasure = true; 656 } 657 658 void animateOutlinesAndSidePages(final boolean show) { 659 animateOutlinesAndSidePages(show, -1); 660 } 661 662 public void setWidgetToResetOnPageFadeOut(int widget) { 663 mWidgetToResetAfterFadeOut = widget; 664 } 665 666 public int getWidgetToResetOnPageFadeOut() { 667 return mWidgetToResetAfterFadeOut; 668 } 669 670 void animateOutlinesAndSidePages(final boolean show, int duration) { 671 if (mChildrenOutlineFadeAnimation != null) { 672 mChildrenOutlineFadeAnimation.cancel(); 673 mChildrenOutlineFadeAnimation = null; 674 } 675 int count = getChildCount(); 676 PropertyValuesHolder alpha; 677 ArrayList<Animator> anims = new ArrayList<Animator>(); 678 679 if (duration == -1) { 680 duration = show ? CHILDREN_OUTLINE_FADE_IN_DURATION : 681 CHILDREN_OUTLINE_FADE_OUT_DURATION; 682 } 683 684 int curPage = getNextPage(); 685 for (int i = 0; i < count; i++) { 686 float finalContentAlpha; 687 if (show) { 688 finalContentAlpha = getAlphaForPage(mScreenCenter, i, true); 689 } else if (!show && i == curPage) { 690 finalContentAlpha = 1f; 691 } else { 692 finalContentAlpha = 0f; 693 } 694 KeyguardWidgetFrame child = getWidgetPageAt(i); 695 696 alpha = PropertyValuesHolder.ofFloat("contentAlpha", finalContentAlpha); 697 ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(child, alpha); 698 anims.add(a); 699 700 float finalOutlineAlpha = show ? getOutlineAlphaForPage(mScreenCenter, i, true) : 0f; 701 child.fadeFrame(this, show, finalOutlineAlpha, duration); 702 } 703 704 mChildrenOutlineFadeAnimation = new AnimatorSet(); 705 mChildrenOutlineFadeAnimation.playTogether(anims); 706 707 mChildrenOutlineFadeAnimation.setDuration(duration); 708 mChildrenOutlineFadeAnimation.addListener(new AnimatorListenerAdapter() { 709 @Override 710 public void onAnimationStart(Animator animation) { 711 if (show) { 712 enablePageContentLayers(); 713 } 714 } 715 716 @Override 717 public void onAnimationEnd(Animator animation) { 718 if (!show) { 719 disablePageContentLayers(); 720 KeyguardWidgetFrame frame = getWidgetPageAt(mWidgetToResetAfterFadeOut); 721 if (frame != null && !(frame == getWidgetPageAt(mCurrentPage) && 722 mViewStateManager.isChallengeOverlapping())) { 723 frame.resetSize(); 724 } 725 mWidgetToResetAfterFadeOut = -1; 726 mShowingInitialHints = false; 727 } 728 updateWidgetFramesImportantForAccessibility(); 729 } 730 }); 731 mChildrenOutlineFadeAnimation.start(); 732 } 733 734 @Override 735 public boolean onLongClick(View v) { 736 // Disallow long pressing to reorder if the challenge is showing 737 boolean isChallengeOverlapping = mViewStateManager.isChallengeShowing() && 738 mViewStateManager.isChallengeOverlapping(); 739 if (!isChallengeOverlapping && startReordering()) { 740 return true; 741 } 742 return false; 743 } 744 745 public void removeWidget(View view) { 746 if (view instanceof KeyguardWidgetFrame) { 747 removeView(view); 748 } else { 749 // Assume view was wrapped by a KeyguardWidgetFrame in KeyguardWidgetPager#addWidget(). 750 // This supports legacy hard-coded "widgets" like KeyguardTransportControlView. 751 int pos = getWidgetPageIndex(view); 752 if (pos != -1) { 753 KeyguardWidgetFrame frame = (KeyguardWidgetFrame) getChildAt(pos); 754 frame.removeView(view); 755 removeView(frame); 756 } else { 757 Slog.w(TAG, "removeWidget() can't find:" + view); 758 } 759 } 760 } 761 762 public int getWidgetPageIndex(View view) { 763 if (view instanceof KeyguardWidgetFrame) { 764 return indexOfChild(view); 765 } else { 766 // View was wrapped by a KeyguardWidgetFrame by KeyguardWidgetPager#addWidget() 767 return indexOfChild((KeyguardWidgetFrame)view.getParent()); 768 } 769 } 770 771 @Override 772 protected void setPageHoveringOverDeleteDropTarget(int viewIndex, boolean isHovering) { 773 KeyguardWidgetFrame child = getWidgetPageAt(viewIndex); 774 child.setIsHoveringOverDeleteDropTarget(isHovering); 775 } 776 777 // ChallengeLayout.OnBouncerStateChangedListener 778 @Override 779 public void onBouncerStateChanged(boolean bouncerActive) { 780 if (bouncerActive) { 781 zoomOutToBouncer(); 782 } else { 783 zoomInFromBouncer(); 784 } 785 } 786 787 void setBouncerAnimationDuration(int duration) { 788 mBouncerZoomInOutDuration = duration; 789 } 790 791 // Zoom in after the bouncer is dismissed 792 void zoomInFromBouncer() { 793 if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) { 794 mZoomInOutAnim.cancel(); 795 } 796 final View currentPage = getPageAt(getCurrentPage()); 797 if (currentPage.getScaleX() < 1f || currentPage.getScaleY() < 1f) { 798 mZoomInOutAnim = new AnimatorSet(); 799 mZoomInOutAnim.playTogether( 800 ObjectAnimator.ofFloat(currentPage, "scaleX", 1f), 801 ObjectAnimator.ofFloat(currentPage , "scaleY", 1f)); 802 mZoomInOutAnim.setDuration(mBouncerZoomInOutDuration); 803 mZoomInOutAnim.setInterpolator(new DecelerateInterpolator(1.5f)); 804 mZoomInOutAnim.start(); 805 } 806 if (currentPage instanceof KeyguardWidgetFrame) { 807 ((KeyguardWidgetFrame)currentPage).onBouncerShowing(false); 808 } 809 } 810 811 // Zoom out after the bouncer is initiated 812 void zoomOutToBouncer() { 813 if (mZoomInOutAnim != null && mZoomInOutAnim.isRunning()) { 814 mZoomInOutAnim.cancel(); 815 } 816 int curPage = getCurrentPage(); 817 View currentPage = getPageAt(curPage); 818 if (shouldSetTopAlignedPivotForWidget(curPage)) { 819 currentPage.setPivotY(0); 820 // Note: we are working around the issue that setting the x-pivot to the same value as it 821 // was does not actually work. 822 currentPage.setPivotX(0); 823 currentPage.setPivotX(currentPage.getMeasuredWidth() / 2); 824 } 825 if (!(currentPage.getScaleX() < 1f || currentPage.getScaleY() < 1f)) { 826 mZoomInOutAnim = new AnimatorSet(); 827 mZoomInOutAnim.playTogether( 828 ObjectAnimator.ofFloat(currentPage, "scaleX", BOUNCER_SCALE_FACTOR), 829 ObjectAnimator.ofFloat(currentPage, "scaleY", BOUNCER_SCALE_FACTOR)); 830 mZoomInOutAnim.setDuration(mBouncerZoomInOutDuration); 831 mZoomInOutAnim.setInterpolator(new DecelerateInterpolator(1.5f)); 832 mZoomInOutAnim.start(); 833 } 834 if (currentPage instanceof KeyguardWidgetFrame) { 835 ((KeyguardWidgetFrame)currentPage).onBouncerShowing(true); 836 } 837 } 838 839 void setAddWidgetEnabled(boolean enabled) { 840 if (mAddWidgetView != null && enabled) { 841 addView(mAddWidgetView, 0); 842 // We need to force measure the PagedView so that the calls to update the scroll 843 // position below work 844 measure(mLastWidthMeasureSpec, mLastHeightMeasureSpec); 845 // Bump up the current page to account for the addition of the new page 846 setCurrentPage(mCurrentPage + 1); 847 mAddWidgetView = null; 848 } else if (mAddWidgetView == null && !enabled) { 849 View addWidget = findViewById(R.id.keyguard_add_widget); 850 if (addWidget != null) { 851 mAddWidgetView = addWidget; 852 removeView(addWidget); 853 } 854 } 855 } 856 857 boolean isAddPage(int pageIndex) { 858 View v = getChildAt(pageIndex); 859 return v != null && v.getId() == R.id.keyguard_add_widget; 860 } 861 862 boolean isCameraPage(int pageIndex) { 863 View v = getChildAt(pageIndex); 864 return v != null && v instanceof CameraWidgetFrame; 865 } 866 867 @Override 868 protected boolean shouldSetTopAlignedPivotForWidget(int childIndex) { 869 return !isCameraPage(childIndex) && super.shouldSetTopAlignedPivotForWidget(childIndex); 870 } 871 872 /** 873 * Search given {@link View} hierarchy for {@link TextClock} instances that 874 * show various time components. Returns combination of 875 * {@link #FLAG_HAS_LOCAL_HOUR} and {@link #FLAG_HAS_LOCAL_MINUTE}. 876 */ 877 private static int findClockInHierarchy(View view) { 878 if (view instanceof TextClock) { 879 return getClockFlags((TextClock) view); 880 } else if (view instanceof ViewGroup) { 881 int flags = 0; 882 final ViewGroup group = (ViewGroup) view; 883 final int size = group.getChildCount(); 884 for (int i = 0; i < size; i++) { 885 flags |= findClockInHierarchy(group.getChildAt(i)); 886 } 887 return flags; 888 } else { 889 return 0; 890 } 891 } 892 893 /** 894 * Return combination of {@link #FLAG_HAS_LOCAL_HOUR} and 895 * {@link #FLAG_HAS_LOCAL_MINUTE} describing the time represented described 896 * by the given {@link TextClock}. 897 */ 898 private static int getClockFlags(TextClock clock) { 899 int flags = 0; 900 901 final String timeZone = clock.getTimeZone(); 902 if (timeZone != null && !TimeZone.getDefault().equals(TimeZone.getTimeZone(timeZone))) { 903 // Ignore clocks showing another timezone 904 return 0; 905 } 906 907 final CharSequence format = clock.getFormat(); 908 final char hour = clock.is24HourModeEnabled() ? DateFormat.HOUR_OF_DAY 909 : DateFormat.HOUR; 910 911 if (DateFormat.hasDesignator(format, hour)) { 912 flags |= FLAG_HAS_LOCAL_HOUR; 913 } 914 if (DateFormat.hasDesignator(format, DateFormat.MINUTE)) { 915 flags |= FLAG_HAS_LOCAL_MINUTE; 916 } 917 918 return flags; 919 } 920} 921