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