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