KeyguardWidgetPager.java revision 70009e426a39cc2940d264c4fb87a4402c60b0ff
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.internal.policy.impl.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.content.Context; 26import android.content.res.Resources; 27import android.os.Handler; 28import android.os.HandlerThread; 29import android.util.AttributeSet; 30import android.view.Gravity; 31import android.view.MotionEvent; 32import android.view.View; 33import android.view.View.OnLongClickListener; 34import android.view.ViewGroup; 35import android.widget.FrameLayout; 36 37import com.android.internal.R; 38import com.android.internal.widget.LockPatternUtils; 39 40import java.util.ArrayList; 41 42public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwitchListener, 43 OnLongClickListener { 44 45 ZInterpolator mZInterpolator = new ZInterpolator(0.5f); 46 private static float CAMERA_DISTANCE = 10000; 47 protected static float OVERSCROLL_MAX_ROTATION = 30; 48 private static final boolean PERFORM_OVERSCROLL_ROTATION = true; 49 50 protected KeyguardViewStateManager mViewStateManager; 51 private LockPatternUtils mLockPatternUtils; 52 53 // Related to the fading in / out background outlines 54 private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375; 55 private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 75; 56 protected AnimatorSet mChildrenOutlineFadeAnimation; 57 private float mChildrenOutlineAlpha = 0; 58 private float mSidePagesAlpha = 1f; 59 protected int mScreenCenter; 60 private boolean mHasLayout = false; 61 private boolean mHasMeasure = false; 62 boolean showHintsAfterLayout = false; 63 64 private static final long CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT = 30000; 65 66 private int mPage = 0; 67 private Callbacks mCallbacks; 68 69 private boolean mCameraWidgetEnabled; 70 71 // Background threads to deal with persistence 72 private HandlerThread mBgPersistenceWorkerThread; 73 private Handler mBgPersistenceWorkerHandler; 74 75 public KeyguardWidgetPager(Context context, AttributeSet attrs) { 76 this(context, attrs, 0); 77 } 78 79 public KeyguardWidgetPager(Context context) { 80 this(null, null, 0); 81 } 82 83 public KeyguardWidgetPager(Context context, AttributeSet attrs, int defStyle) { 84 super(context, attrs, defStyle); 85 if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 86 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 87 } 88 89 setPageSwitchListener(this); 90 91 Resources r = getResources(); 92 mCameraWidgetEnabled = r.getBoolean(R.bool.kg_enable_camera_default_widget); 93 mBgPersistenceWorkerThread = new HandlerThread("KeyguardWidgetPager Persistence"); 94 mBgPersistenceWorkerThread.start(); 95 mBgPersistenceWorkerHandler = new Handler(mBgPersistenceWorkerThread.getLooper()); 96 } 97 98 public void setViewStateManager(KeyguardViewStateManager viewStateManager) { 99 mViewStateManager = viewStateManager; 100 } 101 102 public void setLockPatternUtils(LockPatternUtils l) { 103 mLockPatternUtils = l; 104 } 105 106 @Override 107 public void onPageSwitch(View newPage, int newPageIndex) { 108 boolean showingStatusWidget = false; 109 if (newPage instanceof ViewGroup) { 110 ViewGroup vg = (ViewGroup) newPage; 111 if (vg.getChildAt(0) instanceof KeyguardStatusView) { 112 showingStatusWidget = true; 113 } 114 } 115 116 // Disable the status bar clock if we're showing the default status widget 117 if (showingStatusWidget) { 118 setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_CLOCK); 119 } else { 120 setSystemUiVisibility(getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_CLOCK); 121 } 122 123 // Extend the display timeout if the user switches pages 124 if (mPage != newPageIndex) { 125 int oldPageIndex = mPage; 126 mPage = newPageIndex; 127 userActivity(); 128 KeyguardWidgetFrame oldWidgetPage = getWidgetPageAt(oldPageIndex); 129 if (oldWidgetPage != null) { 130 oldWidgetPage.onActive(false); 131 } 132 KeyguardWidgetFrame newWidgetPage = getWidgetPageAt(newPageIndex); 133 if (newWidgetPage != null) { 134 newWidgetPage.onActive(true); 135 } 136 } 137 if (mViewStateManager != null) { 138 mViewStateManager.onPageSwitch(newPage, newPageIndex); 139 } 140 } 141 142 private void userActivity() { 143 if (mCallbacks != null) { 144 mCallbacks.onUserActivityTimeoutChanged(); 145 mCallbacks.userActivity(); 146 } 147 } 148 149 @Override 150 public boolean onTouchEvent(MotionEvent ev) { 151 KeyguardWidgetFrame currentWidgetPage = getWidgetPageAt(getCurrentPage()); 152 if (currentWidgetPage != null && currentWidgetPage.onUserInteraction(ev.getAction())) { 153 return true; 154 } 155 return super.onTouchEvent(ev); 156 } 157 158 public void showPagingFeedback() { 159 // Nothing yet. 160 } 161 162 public long getUserActivityTimeout() { 163 View page = getPageAt(mPage); 164 if (page instanceof ViewGroup) { 165 ViewGroup vg = (ViewGroup) page; 166 View view = vg.getChildAt(0); 167 if (!(view instanceof KeyguardStatusView) 168 && !(view instanceof KeyguardMultiUserSelectorView)) { 169 return CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT; 170 } 171 } 172 return -1; 173 } 174 175 public void setCallbacks(Callbacks callbacks) { 176 mCallbacks = callbacks; 177 } 178 179 public interface Callbacks { 180 public void userActivity(); 181 public void onUserActivityTimeoutChanged(); 182 } 183 184 public void addWidget(View widget) { 185 addWidget(widget, -1); 186 } 187 188 189 public void onRemoveView(View v) { 190 final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); 191 mBgPersistenceWorkerHandler.post(new Runnable() { 192 @Override 193 public void run() { 194 mLockPatternUtils.removeAppWidget(appWidgetId); 195 } 196 }); 197 } 198 199 public void onAddView(View v, final int index) { 200 final int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); 201 final int[] pagesRange = new int[mTempVisiblePagesRange.length]; 202 getVisiblePages(pagesRange); 203 boundByReorderablePages(true, pagesRange); 204 // Subtract from the index to take into account pages before the reorderable 205 // pages (e.g. the "add widget" page) 206 mBgPersistenceWorkerHandler.post(new Runnable() { 207 @Override 208 public void run() { 209 mLockPatternUtils.addAppWidget(appWidgetId, index - pagesRange[0]); 210 } 211 }); 212 } 213 214 /* 215 * We wrap widgets in a special frame which handles drawing the over scroll foreground. 216 */ 217 public void addWidget(View widget, int pageIndex) { 218 KeyguardWidgetFrame frame; 219 // All views contained herein should be wrapped in a KeyguardWidgetFrame 220 if (!(widget instanceof KeyguardWidgetFrame)) { 221 frame = new KeyguardWidgetFrame(getContext()); 222 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, 223 LayoutParams.MATCH_PARENT); 224 lp.gravity = Gravity.TOP; 225 // The framework adds a default padding to AppWidgetHostView. We don't need this padding 226 // for the Keyguard, so we override it to be 0. 227 widget.setPadding(0, 0, 0, 0); 228 if (widget instanceof AppWidgetHostView) { 229 AppWidgetHostView awhv = (AppWidgetHostView) widget; 230 widget.setContentDescription(awhv.getAppWidgetInfo().label); 231 } 232 frame.addView(widget, lp); 233 } else { 234 frame = (KeyguardWidgetFrame) widget; 235 } 236 237 ViewGroup.LayoutParams pageLp = new ViewGroup.LayoutParams( 238 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 239 frame.setOnLongClickListener(this); 240 241 if (pageIndex == -1) { 242 addView(frame, pageLp); 243 } else { 244 addView(frame, pageIndex, pageLp); 245 } 246 } 247 248 // We enforce that all children are KeyguardWidgetFrames 249 @Override 250 public void addView(View child, int index) { 251 enforceKeyguardWidgetFrame(child); 252 super.addView(child, index); 253 } 254 255 @Override 256 public void addView(View child, int width, int height) { 257 enforceKeyguardWidgetFrame(child); 258 super.addView(child, width, height); 259 } 260 261 @Override 262 public void addView(View child, LayoutParams params) { 263 enforceKeyguardWidgetFrame(child); 264 super.addView(child, params); 265 } 266 267 @Override 268 public void addView(View child, int index, LayoutParams params) { 269 enforceKeyguardWidgetFrame(child); 270 super.addView(child, index, params); 271 } 272 273 private void enforceKeyguardWidgetFrame(View child) { 274 if (!(child instanceof KeyguardWidgetFrame)) { 275 throw new IllegalArgumentException( 276 "KeyguardWidgetPager children must be KeyguardWidgetFrames"); 277 } 278 } 279 280 public KeyguardWidgetFrame getWidgetPageAt(int index) { 281 // This is always a valid cast as we've guarded the ability to 282 return (KeyguardWidgetFrame) getChildAt(index); 283 } 284 285 protected void onUnhandledTap(MotionEvent ev) { 286 showPagingFeedback(); 287 } 288 289 @Override 290 protected void onPageBeginMoving() { 291 if (mViewStateManager != null) { 292 mViewStateManager.onPageBeginMoving(); 293 } 294 if (!isReordering(false)) { 295 showOutlinesAndSidePages(); 296 } 297 userActivity(); 298 } 299 300 @Override 301 protected void onPageEndMoving() { 302 if (mViewStateManager != null) { 303 mViewStateManager.onPageEndMoving(); 304 } 305 306 // In the reordering case, the pages will be faded appropriately on completion 307 // of the zoom in animation. 308 if (!isReordering(false)) { 309 hideOutlinesAndSidePages(); 310 } 311 } 312 313 protected void enablePageLayers() { 314 int children = getChildCount(); 315 for (int i = 0; i < children; i++) { 316 getWidgetPageAt(i).enableHardwareLayersForContent(); 317 } 318 } 319 320 protected void disablePageLayers() { 321 int children = getChildCount(); 322 for (int i = 0; i < children; i++) { 323 getWidgetPageAt(i).disableHardwareLayersForContent(); 324 } 325 } 326 327 /* 328 * This interpolator emulates the rate at which the perceived scale of an object changes 329 * as its distance from a camera increases. When this interpolator is applied to a scale 330 * animation on a view, it evokes the sense that the object is shrinking due to moving away 331 * from the camera. 332 */ 333 static class ZInterpolator implements TimeInterpolator { 334 private float focalLength; 335 336 public ZInterpolator(float foc) { 337 focalLength = foc; 338 } 339 340 public float getInterpolation(float input) { 341 return (1.0f - focalLength / (focalLength + input)) / 342 (1.0f - focalLength / (focalLength + 1.0f)); 343 } 344 } 345 346 @Override 347 public String getCurrentPageDescription() { 348 final int nextPageIndex = getNextPage(); 349 if (nextPageIndex >= 0 && nextPageIndex < getChildCount()) { 350 KeyguardWidgetFrame frame = getWidgetPageAt(nextPageIndex); 351 CharSequence title = frame.getChildAt(0).getContentDescription(); 352 if (title == null) { 353 title = ""; 354 } 355 return mContext.getString( 356 com.android.internal.R.string.keyguard_accessibility_widget_changed, 357 title, nextPageIndex + 1, getChildCount()); 358 } 359 return super.getCurrentPageDescription(); 360 } 361 362 @Override 363 protected void overScroll(float amount) { 364 acceleratedOverScroll(amount); 365 } 366 367 float backgroundAlphaInterpolator(float r) { 368 return Math.min(1f, r); 369 } 370 371 private void updatePageAlphaValues(int screenCenter) { 372 } 373 374 public float getAlphaForPage(int screenCenter, int index) { 375 return 1f; 376 } 377 378 public float getOutlineAlphaForPage(int screenCenter, int index) { 379 return getAlphaForPage(screenCenter, index) * KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER; 380 } 381 382 protected boolean isOverScrollChild(int index, float scrollProgress) { 383 boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX; 384 return (isInOverscroll && (index == 0 && scrollProgress < 0 || 385 index == getChildCount() - 1 && scrollProgress > 0)); 386 } 387 388 @Override 389 protected void screenScrolled(int screenCenter) { 390 mScreenCenter = screenCenter; 391 updatePageAlphaValues(screenCenter); 392 for (int i = 0; i < getChildCount(); i++) { 393 KeyguardWidgetFrame v = getWidgetPageAt(i); 394 if (v == mDragView) continue; 395 if (v != null) { 396 float scrollProgress = getScrollProgress(screenCenter, v, i); 397 398 v.setCameraDistance(mDensity * CAMERA_DISTANCE); 399 400 if (isOverScrollChild(i, scrollProgress) && PERFORM_OVERSCROLL_ROTATION) { 401 v.setRotationY(- OVERSCROLL_MAX_ROTATION * scrollProgress); 402 v.setOverScrollAmount(Math.abs(scrollProgress), scrollProgress < 0); 403 } else { 404 v.setRotationY(0f); 405 v.setOverScrollAmount(0, false); 406 } 407 408 float alpha = v.getAlpha(); 409 // If the view has 0 alpha, we set it to be invisible so as to prevent 410 // it from accepting touches 411 if (alpha == 0) { 412 v.setVisibility(INVISIBLE); 413 } else if (v.getVisibility() != VISIBLE) { 414 v.setVisibility(VISIBLE); 415 } 416 } 417 } 418 } 419 420 @Override 421 void boundByReorderablePages(boolean isReordering, int[] range) { 422 if (isReordering) { 423 if (isAddWidgetPageVisible()) { 424 range[0]++; 425 } 426 if (isMusicWidgetVisible()) { 427 range[1]--; 428 } 429 if (isCameraWidgetVisible()) { 430 range[1]--; 431 } 432 } 433 } 434 435 /* 436 * Special widgets 437 */ 438 boolean isAddWidgetPageVisible() { 439 // TODO: Make proper test once we decide whether the add-page is always showing 440 return true; 441 } 442 boolean isMusicWidgetVisible() { 443 // TODO: Make proper test once we have music in the list 444 return false; 445 } 446 boolean isCameraWidgetVisible() { 447 return mCameraWidgetEnabled; 448 } 449 450 protected void reorderStarting() { 451 showOutlinesAndSidePages(); 452 } 453 454 @Override 455 protected void onStartReordering() { 456 super.onStartReordering(); 457 enablePageLayers(); 458 reorderStarting(); 459 } 460 461 @Override 462 protected void onEndReordering() { 463 super.onEndReordering(); 464 hideOutlinesAndSidePages(); 465 } 466 467 void showOutlinesAndSidePages() { 468 animateOutlinesAndSidePages(true); 469 } 470 471 void hideOutlinesAndSidePages() { 472 animateOutlinesAndSidePages(false); 473 } 474 475 public void showInitialPageHints() { 476 if (mHasLayout) { 477 showOutlinesAndSidePages(); 478 } else { 479 // The layout hints depend on layout being run once 480 showHintsAfterLayout = true; 481 } 482 } 483 484 @Override 485 public void onAttachedToWindow() { 486 super.onAttachedToWindow(); 487 mHasMeasure = false; 488 mHasLayout = false; 489 } 490 491 @Override 492 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 493 super.onLayout(changed, left, top, right, bottom); 494 if (showHintsAfterLayout) { 495 post(new Runnable() { 496 @Override 497 public void run() { 498 showOutlinesAndSidePages(); 499 } 500 }); 501 showHintsAfterLayout = false; 502 } 503 mHasLayout = true; 504 } 505 506 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 507 int maxChallengeTop = -1; 508 View parent = (View) getParent(); 509 boolean challengeShowing = false; 510 // Widget pages need to know where the top of the sliding challenge is so that they 511 // now how big the widget should be when the challenge is up. We compute it here and 512 // then propagate it to each of our children. 513 if (parent.getParent() instanceof SlidingChallengeLayout) { 514 SlidingChallengeLayout scl = (SlidingChallengeLayout) parent.getParent(); 515 int top = scl.getMaxChallengeTop(); 516 517 // This is a bit evil, but we need to map a coordinate relative to the SCL into a 518 // coordinate relative to our children, hence we subtract the top padding.s 519 maxChallengeTop = top - getPaddingTop(); 520 challengeShowing = scl.isChallengeShowing(); 521 } 522 523 int count = getChildCount(); 524 for (int i = 0; i < count; i++) { 525 KeyguardWidgetFrame frame = getWidgetPageAt(i); 526 frame.setMaxChallengeTop(maxChallengeTop); 527 528 // On the very first measure pass, if the challenge is showing, we need to make sure 529 // that the widget on the current page is small. 530 if (challengeShowing && i == mCurrentPage && !mHasMeasure) { 531 frame.shrinkWidget(); 532 } 533 } 534 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 535 } 536 537 void animateOutlinesAndSidePages(final boolean show) { 538 animateOutlinesAndSidePages(show, -1); 539 } 540 541 void animateOutlinesAndSidePages(final boolean show, int duration) { 542 if (mChildrenOutlineFadeAnimation != null) { 543 mChildrenOutlineFadeAnimation.cancel(); 544 mChildrenOutlineFadeAnimation = null; 545 } 546 int count = getChildCount(); 547 PropertyValuesHolder alpha; 548 ArrayList<Animator> anims = new ArrayList<Animator>(); 549 550 if (duration == -1) { 551 duration = show ? CHILDREN_OUTLINE_FADE_IN_DURATION : 552 CHILDREN_OUTLINE_FADE_OUT_DURATION; 553 } 554 555 int curPage = getNextPage(); 556 for (int i = 0; i < count; i++) { 557 float finalContentAlpha; 558 if (show) { 559 finalContentAlpha = getAlphaForPage(mScreenCenter, i); 560 } else if (!show && i == curPage) { 561 finalContentAlpha = 1f; 562 } else { 563 finalContentAlpha = 0f; 564 } 565 KeyguardWidgetFrame child = getWidgetPageAt(i); 566 alpha = PropertyValuesHolder.ofFloat("contentAlpha", finalContentAlpha); 567 ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(child, alpha); 568 anims.add(a); 569 570 float finalOutlineAlpha = show ? getOutlineAlphaForPage(mScreenCenter, i) : 0f; 571 child.fadeFrame(this, show, finalOutlineAlpha, duration); 572 } 573 574 mChildrenOutlineFadeAnimation = new AnimatorSet(); 575 mChildrenOutlineFadeAnimation.playTogether(anims); 576 577 mChildrenOutlineFadeAnimation.setDuration(duration); 578 mChildrenOutlineFadeAnimation.addListener(new AnimatorListenerAdapter() { 579 @Override 580 public void onAnimationStart(Animator animation) { 581 if (show) { 582 enablePageLayers(); 583 } 584 } 585 @Override 586 public void onAnimationEnd(Animator animation) { 587 if (!show) { 588 disablePageLayers(); 589 } 590 } 591 }); 592 mChildrenOutlineFadeAnimation.start(); 593 } 594 595 public void setChildrenOutlineAlpha(float alpha) { 596 mChildrenOutlineAlpha = alpha; 597 for (int i = 0; i < getChildCount(); i++) { 598 getWidgetPageAt(i).setBackgroundAlpha(alpha); 599 } 600 } 601 602 public void setSidePagesAlpha(float alpha) { 603 // This gives the current page, or the destination page if in transit. 604 int curPage = getNextPage(); 605 mSidePagesAlpha = alpha; 606 for (int i = 0; i < getChildCount(); i++) { 607 if (curPage != i) { 608 getWidgetPageAt(i).setContentAlpha(alpha); 609 } else { 610 // We lock the current page alpha to 1. 611 getWidgetPageAt(i).setContentAlpha(1.0f); 612 } 613 } 614 } 615 616 public void setChildrenOutlineMultiplier(float alpha) { 617 mChildrenOutlineAlpha = alpha; 618 for (int i = 0; i < getChildCount(); i++) { 619 getWidgetPageAt(i).setBackgroundAlphaMultiplier(alpha); 620 } 621 } 622 623 public float getSidePagesAlpha() { 624 return mSidePagesAlpha; 625 } 626 627 public float getChildrenOutlineAlpha() { 628 return mChildrenOutlineAlpha; 629 } 630 631 @Override 632 public boolean onLongClick(View v) { 633 // Disallow long pressing to reorder if the challenge is showing 634 boolean isChallengeOverlapping = mViewStateManager.isChallengeShowing() && 635 mViewStateManager.isChallengeOverlapping(); 636 if (!isChallengeOverlapping && startReordering()) { 637 return true; 638 } 639 return false; 640 } 641} 642