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