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