KeyguardWidgetPager.java revision f8f5966e591cc39364f0ebccd40896b841289076
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 private 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 private boolean mShowHintsOnLayout = 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 showOutlinesAndSidePages(); 295 userActivity(); 296 } 297 298 @Override 299 protected void onPageEndMoving() { 300 if (mViewStateManager != null) { 301 mViewStateManager.onPageEndMoving(); 302 } 303 hideOutlinesAndSidePages(); 304 } 305 306 private void enablePageLayers() { 307 int children = getChildCount(); 308 for (int i = 0; i < children; i++) { 309 getWidgetPageAt(i).enableHardwareLayersForContent(); 310 } 311 } 312 313 private void disablePageLayers() { 314 int children = getChildCount(); 315 for (int i = 0; i < children; i++) { 316 getWidgetPageAt(i).disableHardwareLayersForContent(); 317 } 318 } 319 320 /* 321 * This interpolator emulates the rate at which the perceived scale of an object changes 322 * as its distance from a camera increases. When this interpolator is applied to a scale 323 * animation on a view, it evokes the sense that the object is shrinking due to moving away 324 * from the camera. 325 */ 326 static class ZInterpolator implements TimeInterpolator { 327 private float focalLength; 328 329 public ZInterpolator(float foc) { 330 focalLength = foc; 331 } 332 333 public float getInterpolation(float input) { 334 return (1.0f - focalLength / (focalLength + input)) / 335 (1.0f - focalLength / (focalLength + 1.0f)); 336 } 337 } 338 339 @Override 340 public String getCurrentPageDescription() { 341 final int nextPageIndex = getNextPage(); 342 if (nextPageIndex >= 0 && nextPageIndex < getChildCount()) { 343 KeyguardWidgetFrame frame = getWidgetPageAt(nextPageIndex); 344 CharSequence title = frame.getChildAt(0).getContentDescription(); 345 if (title == null) { 346 title = ""; 347 } 348 return mContext.getString( 349 com.android.internal.R.string.keyguard_accessibility_widget_changed, 350 title, nextPageIndex + 1, getChildCount()); 351 } 352 return super.getCurrentPageDescription(); 353 } 354 355 @Override 356 protected void overScroll(float amount) { 357 acceleratedOverScroll(amount); 358 } 359 360 float backgroundAlphaInterpolator(float r) { 361 return Math.min(1f, r); 362 } 363 364 private void updatePageAlphaValues(int screenCenter) { 365 } 366 367 public float getAlphaForPage(int screenCenter, int index) { 368 return 1f; 369 } 370 371 public float getOutlineAlphaForPage(int screenCenter, int index) { 372 return getAlphaForPage(screenCenter, index) * KeyguardWidgetFrame.OUTLINE_ALPHA_MULTIPLIER; 373 } 374 375 protected boolean isOverScrollChild(int index, float scrollProgress) { 376 boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX; 377 return (isInOverscroll && (index == 0 && scrollProgress < 0 || 378 index == getChildCount() - 1 && scrollProgress > 0)); 379 } 380 381 @Override 382 protected void screenScrolled(int screenCenter) { 383 mScreenCenter = screenCenter; 384 updatePageAlphaValues(screenCenter); 385 for (int i = 0; i < getChildCount(); i++) { 386 KeyguardWidgetFrame v = getWidgetPageAt(i); 387 if (v == mDragView) continue; 388 if (v != null) { 389 float scrollProgress = getScrollProgress(screenCenter, v, i); 390 391 v.setCameraDistance(mDensity * CAMERA_DISTANCE); 392 393 if (isOverScrollChild(i, scrollProgress) && PERFORM_OVERSCROLL_ROTATION) { 394 v.setRotationY(- OVERSCROLL_MAX_ROTATION * scrollProgress); 395 v.setOverScrollAmount(Math.abs(scrollProgress), scrollProgress < 0); 396 } else { 397 v.setRotationY(0f); 398 v.setOverScrollAmount(0, false); 399 } 400 401 float alpha = v.getAlpha(); 402 // If the view has 0 alpha, we set it to be invisible so as to prevent 403 // it from accepting touches 404 if (alpha == 0) { 405 v.setVisibility(INVISIBLE); 406 } else if (v.getVisibility() != VISIBLE) { 407 v.setVisibility(VISIBLE); 408 } 409 } 410 } 411 } 412 413 @Override 414 void boundByReorderablePages(boolean isReordering, int[] range) { 415 if (isReordering) { 416 if (isAddWidgetPageVisible()) { 417 range[0]++; 418 } 419 if (isMusicWidgetVisible()) { 420 range[1]--; 421 } 422 if (isCameraWidgetVisible()) { 423 range[1]--; 424 } 425 } 426 } 427 428 /* 429 * Special widgets 430 */ 431 boolean isAddWidgetPageVisible() { 432 // TODO: Make proper test once we decide whether the add-page is always showing 433 return true; 434 } 435 boolean isMusicWidgetVisible() { 436 // TODO: Make proper test once we have music in the list 437 return false; 438 } 439 boolean isCameraWidgetVisible() { 440 return mCameraWidgetEnabled; 441 } 442 443 @Override 444 protected void onStartReordering() { 445 super.onStartReordering(); 446 showOutlinesAndSidePages(); 447 } 448 449 @Override 450 protected void onEndReordering() { 451 super.onEndReordering(); 452 hideOutlinesAndSidePages(); 453 } 454 455 void showOutlinesAndSidePages() { 456 enablePageLayers(); 457 animateOutlinesAndSidePages(true); 458 } 459 460 void hideOutlinesAndSidePages() { 461 animateOutlinesAndSidePages(false); 462 } 463 464 public void showInitialPageHints() { 465 if (mHasLayout) { 466 showOutlinesAndSidePages(); 467 } else { 468 // The layout hints depend on layout being run once 469 mShowHintsOnLayout = true; 470 } 471 } 472 473 @Override 474 public void onAttachedToWindow() { 475 super.onAttachedToWindow(); 476 mHasMeasure = false; 477 mHasLayout = false; 478 } 479 480 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 481 super.onLayout(changed, left, top, right, bottom); 482 if (mShowHintsOnLayout) { 483 post(new Runnable() { 484 @Override 485 public void run() { 486 showOutlinesAndSidePages(); 487 } 488 }); 489 mShowHintsOnLayout = false; 490 } 491 mHasLayout = true; 492 } 493 494 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 495 int maxChallengeTop = -1; 496 View parent = (View) getParent(); 497 boolean challengeShowing = false; 498 // Widget pages need to know where the top of the sliding challenge is so that they 499 // now how big the widget should be when the challenge is up. We compute it here and 500 // then propagate it to each of our children. 501 if (parent.getParent() instanceof SlidingChallengeLayout) { 502 SlidingChallengeLayout scl = (SlidingChallengeLayout) parent.getParent(); 503 int top = scl.getMaxChallengeTop(); 504 505 // This is a bit evil, but we need to map a coordinate relative to the SCL into a 506 // coordinate relative to our children, hence we subtract the top padding.s 507 maxChallengeTop = top - getPaddingTop(); 508 challengeShowing = scl.isChallengeShowing(); 509 } 510 511 int count = getChildCount(); 512 for (int i = 0; i < count; i++) { 513 KeyguardWidgetFrame frame = getWidgetPageAt(i); 514 frame.setMaxChallengeTop(maxChallengeTop); 515 516 // On the very first measure pass, if the challenge is showing, we need to make sure 517 // that the widget on the current page is small. 518 if (challengeShowing && i == mCurrentPage && !mHasMeasure) { 519 frame.shrinkWidget(); 520 } 521 } 522 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 523 } 524 525 void animateOutlinesAndSidePages(final boolean show) { 526 if (mChildrenOutlineFadeAnimation != null) { 527 mChildrenOutlineFadeAnimation.cancel(); 528 mChildrenOutlineFadeAnimation = null; 529 } 530 531 int count = getChildCount(); 532 PropertyValuesHolder alpha; 533 ArrayList<Animator> anims = new ArrayList<Animator>(); 534 535 int duration = show ? CHILDREN_OUTLINE_FADE_IN_DURATION : 536 CHILDREN_OUTLINE_FADE_OUT_DURATION; 537 538 int curPage = getNextPage(); 539 for (int i = 0; i < count; i++) { 540 float finalContentAlpha; 541 if (show) { 542 finalContentAlpha = getAlphaForPage(mScreenCenter, i); 543 } else if (!show && i == curPage) { 544 finalContentAlpha = 1f; 545 } else { 546 finalContentAlpha = 0f; 547 } 548 KeyguardWidgetFrame child = getWidgetPageAt(i); 549 alpha = PropertyValuesHolder.ofFloat("contentAlpha", finalContentAlpha); 550 ObjectAnimator a = ObjectAnimator.ofPropertyValuesHolder(child, alpha); 551 anims.add(a); 552 553 float finalOutlineAlpha = show ? getOutlineAlphaForPage(mScreenCenter, i) : 0f; 554 child.fadeFrame(this, show, finalOutlineAlpha, duration); 555 } 556 557 mChildrenOutlineFadeAnimation = new AnimatorSet(); 558 mChildrenOutlineFadeAnimation.playTogether(anims); 559 560 mChildrenOutlineFadeAnimation.setDuration(duration); 561 mChildrenOutlineFadeAnimation.addListener(new AnimatorListenerAdapter() { 562 @Override 563 public void onAnimationEnd(Animator animation) { 564 if (!show) { 565 disablePageLayers(); 566 } 567 } 568 }); 569 mChildrenOutlineFadeAnimation.start(); 570 } 571 572 public void setChildrenOutlineAlpha(float alpha) { 573 mChildrenOutlineAlpha = alpha; 574 for (int i = 0; i < getChildCount(); i++) { 575 getWidgetPageAt(i).setBackgroundAlpha(alpha); 576 } 577 } 578 579 public void setSidePagesAlpha(float alpha) { 580 // This gives the current page, or the destination page if in transit. 581 int curPage = getNextPage(); 582 mSidePagesAlpha = alpha; 583 for (int i = 0; i < getChildCount(); i++) { 584 if (curPage != i) { 585 getWidgetPageAt(i).setContentAlpha(alpha); 586 } else { 587 // We lock the current page alpha to 1. 588 getWidgetPageAt(i).setContentAlpha(1.0f); 589 } 590 } 591 } 592 593 public void setChildrenOutlineMultiplier(float alpha) { 594 mChildrenOutlineAlpha = alpha; 595 for (int i = 0; i < getChildCount(); i++) { 596 getWidgetPageAt(i).setBackgroundAlphaMultiplier(alpha); 597 } 598 } 599 600 public float getSidePagesAlpha() { 601 return mSidePagesAlpha; 602 } 603 604 public float getChildrenOutlineAlpha() { 605 return mChildrenOutlineAlpha; 606 } 607 608 @Override 609 public boolean onLongClick(View v) { 610 // Disallow long pressing to reorder if the challenge is showing 611 if (!mViewStateManager.isChallengeShowing() && startReordering()) { 612 return true; 613 } 614 return false; 615 } 616} 617