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