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