KeyguardWidgetPager.java revision 9ec871def38079691085a16862c234b98de99362
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.ObjectAnimator; 19import android.animation.PropertyValuesHolder; 20import android.animation.TimeInterpolator; 21import android.appwidget.AppWidgetHostView; 22import android.content.Context; 23import android.util.AttributeSet; 24import android.view.Gravity; 25import android.view.MotionEvent; 26import android.view.View; 27import android.view.View.OnLongClickListener; 28import android.view.ViewGroup; 29import android.view.animation.AccelerateInterpolator; 30import android.view.animation.DecelerateInterpolator; 31import android.widget.FrameLayout; 32 33import com.android.internal.widget.LockPatternUtils; 34 35public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwitchListener, 36 OnLongClickListener { 37 38 ZInterpolator mZInterpolator = new ZInterpolator(0.5f); 39 private static float CAMERA_DISTANCE = 10000; 40 private static float TRANSITION_MAX_ROTATION = 30; 41 private static final boolean PERFORM_OVERSCROLL_ROTATION = true; 42 43 private KeyguardViewStateManager mViewStateManager; 44 private LockPatternUtils mLockPatternUtils; 45 46 // Related to the fading in / out background outlines 47 private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0; 48 private static final int CHILDREN_OUTLINE_FADE_OUT_DURATION = 375; 49 private static final int CHILDREN_OUTLINE_FADE_IN_DURATION = 100; 50 private ObjectAnimator mChildrenOutlineFadeInAnimation; 51 private ObjectAnimator mChildrenOutlineFadeOutAnimation; 52 private float mChildrenOutlineAlpha = 0; 53 private float mSidePagesAlpha = 1f; 54 55 private static final long CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT = 30000; 56 57 private int mPage = 0; 58 private Callbacks mCallbacks; 59 60 public KeyguardWidgetPager(Context context, AttributeSet attrs) { 61 this(context, attrs, 0); 62 } 63 64 public KeyguardWidgetPager(Context context) { 65 this(null, null, 0); 66 } 67 68 public KeyguardWidgetPager(Context context, AttributeSet attrs, int defStyle) { 69 super(context, attrs, defStyle); 70 if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 71 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 72 } 73 74 setPageSwitchListener(this); 75 } 76 77 public void setViewStateManager(KeyguardViewStateManager viewStateManager) { 78 mViewStateManager = viewStateManager; 79 } 80 81 public void setLockPatternUtils(LockPatternUtils l) { 82 mLockPatternUtils = l; 83 } 84 85 @Override 86 public void onPageSwitch(View newPage, int newPageIndex) { 87 boolean showingStatusWidget = false; 88 if (newPage instanceof ViewGroup) { 89 ViewGroup vg = (ViewGroup) newPage; 90 if (vg.getChildAt(0) instanceof KeyguardStatusView) { 91 showingStatusWidget = true; 92 } 93 } 94 95 // Disable the status bar clock if we're showing the default status widget 96 if (showingStatusWidget) { 97 setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_CLOCK); 98 } else { 99 setSystemUiVisibility(getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_CLOCK); 100 } 101 102 // Extend the display timeout if the user switches pages 103 if (mPage != newPageIndex) { 104 int oldPageIndex = mPage; 105 mPage = newPageIndex; 106 if (mCallbacks != null) { 107 mCallbacks.onUserActivityTimeoutChanged(); 108 mCallbacks.userActivity(); 109 mCallbacks.onPageSwitch(newPageIndex); 110 } 111 KeyguardWidgetFrame oldWidgetPage = getWidgetPageAt(oldPageIndex); 112 if (oldWidgetPage != null) { 113 oldWidgetPage.onActive(false); 114 } 115 KeyguardWidgetFrame newWidgetPage = getWidgetPageAt(newPageIndex); 116 if (newWidgetPage != null) { 117 newWidgetPage.onActive(true); 118 } 119 } 120 if (mViewStateManager != null) { 121 mViewStateManager.onPageSwitch(newPage, newPageIndex); 122 } 123 } 124 125 public void showPagingFeedback() { 126 // Nothing yet. 127 } 128 129 public long getUserActivityTimeout() { 130 View page = getPageAt(mPage); 131 if (page instanceof ViewGroup) { 132 ViewGroup vg = (ViewGroup) page; 133 View view = vg.getChildAt(0); 134 if (!(view instanceof KeyguardStatusView) 135 && !(view instanceof KeyguardMultiUserSelectorView)) { 136 return CUSTOM_WIDGET_USER_ACTIVITY_TIMEOUT; 137 } 138 } 139 return -1; 140 } 141 142 public void setCallbacks(Callbacks callbacks) { 143 mCallbacks = callbacks; 144 } 145 146 public interface Callbacks { 147 public void userActivity(); 148 public void onUserActivityTimeoutChanged(); 149 public void onPageSwitch(int newPageIndex); 150 } 151 152 public void addWidget(View widget) { 153 addWidget(widget, -1); 154 } 155 156 157 public void onRemoveView(View v) { 158 int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); 159 mLockPatternUtils.removeAppWidget(appWidgetId); 160 } 161 162 public void onAddView(View v, int index) { 163 int appWidgetId = ((KeyguardWidgetFrame) v).getContentAppWidgetId(); 164 getVisiblePages(mTempVisiblePagesRange); 165 boundByReorderablePages(true, mTempVisiblePagesRange); 166 // Subtract from the index to take into account pages before the reorderable 167 // pages (e.g. the "add widget" page) 168 mLockPatternUtils.addAppWidget(appWidgetId, index - mTempVisiblePagesRange[0]); 169 } 170 171 /* 172 * We wrap widgets in a special frame which handles drawing the over scroll foreground. 173 */ 174 public void addWidget(View widget, int pageIndex) { 175 KeyguardWidgetFrame frame; 176 // All views contained herein should be wrapped in a KeyguardWidgetFrame 177 if (!(widget instanceof KeyguardWidgetFrame)) { 178 frame = new KeyguardWidgetFrame(getContext()); 179 FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, 180 LayoutParams.MATCH_PARENT); 181 lp.gravity = Gravity.TOP; 182 // The framework adds a default padding to AppWidgetHostView. We don't need this padding 183 // for the Keyguard, so we override it to be 0. 184 widget.setPadding(0, 0, 0, 0); 185 if (widget instanceof AppWidgetHostView) { 186 AppWidgetHostView awhv = (AppWidgetHostView) widget; 187 widget.setContentDescription(awhv.getAppWidgetInfo().label); 188 } 189 frame.addView(widget, lp); 190 } else { 191 frame = (KeyguardWidgetFrame) widget; 192 } 193 194 ViewGroup.LayoutParams pageLp = new ViewGroup.LayoutParams( 195 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 196 frame.setOnLongClickListener(this); 197 198 if (pageIndex == -1) { 199 addView(frame, pageLp); 200 } else { 201 addView(frame, pageIndex, pageLp); 202 } 203 } 204 205 // We enforce that all children are KeyguardWidgetFrames 206 @Override 207 public void addView(View child, int index) { 208 enforceKeyguardWidgetFrame(child); 209 super.addView(child, index); 210 } 211 212 @Override 213 public void addView(View child, int width, int height) { 214 enforceKeyguardWidgetFrame(child); 215 super.addView(child, width, height); 216 } 217 218 @Override 219 public void addView(View child, LayoutParams params) { 220 enforceKeyguardWidgetFrame(child); 221 super.addView(child, params); 222 } 223 224 @Override 225 public void addView(View child, int index, LayoutParams params) { 226 enforceKeyguardWidgetFrame(child); 227 super.addView(child, index, params); 228 } 229 230 private void enforceKeyguardWidgetFrame(View child) { 231 if (!(child instanceof KeyguardWidgetFrame)) { 232 throw new IllegalArgumentException( 233 "KeyguardWidgetPager children must be KeyguardWidgetFrames"); 234 } 235 } 236 237 public KeyguardWidgetFrame getWidgetPageAt(int index) { 238 // This is always a valid cast as we've guarded the ability to 239 return (KeyguardWidgetFrame) getChildAt(index); 240 } 241 242 protected void onUnhandledTap(MotionEvent ev) { 243 showPagingFeedback(); 244 } 245 246 @Override 247 protected void onPageBeginMoving() { 248 // Enable hardware layers while pages are moving 249 // TODO: We should only do this for the two views that are actually moving 250 int children = getChildCount(); 251 for (int i = 0; i < children; i++) { 252 getWidgetPageAt(i).enableHardwareLayersForContent(); 253 } 254 255 if (mViewStateManager != null) { 256 mViewStateManager.onPageBeginMoving(); 257 } 258 showOutlinesAndSidePages(); 259 } 260 261 @Override 262 protected void onPageEndMoving() { 263 // Disable hardware layers while pages are moving 264 int children = getChildCount(); 265 for (int i = 0; i < children; i++) { 266 getWidgetPageAt(i).disableHardwareLayersForContent(); 267 } 268 269 if (mViewStateManager != null) { 270 mViewStateManager.onPageEndMoving(); 271 } 272 hideOutlinesAndSidePages(); 273 } 274 275 /* 276 * This interpolator emulates the rate at which the perceived scale of an object changes 277 * as its distance from a camera increases. When this interpolator is applied to a scale 278 * animation on a view, it evokes the sense that the object is shrinking due to moving away 279 * from the camera. 280 */ 281 static class ZInterpolator implements TimeInterpolator { 282 private float focalLength; 283 284 public ZInterpolator(float foc) { 285 focalLength = foc; 286 } 287 288 public float getInterpolation(float input) { 289 return (1.0f - focalLength / (focalLength + input)) / 290 (1.0f - focalLength / (focalLength + 1.0f)); 291 } 292 } 293 294 @Override 295 public String getCurrentPageDescription() { 296 final int nextPageIndex = getNextPage(); 297 if (nextPageIndex >= 0 && nextPageIndex < getChildCount()) { 298 KeyguardWidgetFrame frame = getWidgetPageAt(nextPageIndex); 299 CharSequence title = frame.getChildAt(0).getContentDescription(); 300 if (title == null) { 301 title = ""; 302 } 303 return mContext.getString( 304 com.android.internal.R.string.keyguard_accessibility_widget_changed, 305 title, nextPageIndex + 1, getChildCount()); 306 } 307 return super.getCurrentPageDescription(); 308 } 309 310 @Override 311 protected void overScroll(float amount) { 312 acceleratedOverScroll(amount); 313 } 314 315 float backgroundAlphaInterpolator(float r) { 316 return Math.min(1f, r); 317 } 318 319 private void updatePageAlphaValues(int screenCenter) { 320 boolean isInOverscroll = mOverScrollX < 0 || mOverScrollX > mMaxScrollX; 321 if (!isInOverscroll) { 322 for (int i = 0; i < getChildCount(); i++) { 323 KeyguardWidgetFrame child = getWidgetPageAt(i); 324 if (child != null) { 325 float scrollProgress = getScrollProgress(screenCenter, child, i); 326 if (!isReordering(false)) { 327 child.setBackgroundAlphaMultiplier( 328 backgroundAlphaInterpolator(Math.abs(scrollProgress))); 329 } else { 330 child.setBackgroundAlphaMultiplier(1f); 331 } 332 } 333 } 334 } 335 } 336 337 @Override 338 protected void screenScrolled(int screenCenter) { 339 updatePageAlphaValues(screenCenter); 340 for (int i = 0; i < getChildCount(); i++) { 341 KeyguardWidgetFrame v = getWidgetPageAt(i); 342 if (v == mDragView) continue; 343 if (v != null) { 344 float scrollProgress = getScrollProgress(screenCenter, v, i); 345 346 float alpha = 1.0f; 347 348 v.setCameraDistance(mDensity * CAMERA_DISTANCE); 349 350 if (PERFORM_OVERSCROLL_ROTATION) { 351 if (i == 0 && scrollProgress < 0) { 352 // Over scroll to the left 353 v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); 354 v.setOverScrollAmount(Math.abs(scrollProgress), true); 355 alpha = 1.0f; 356 // On the first page, we don't want the page to have any lateral motion 357 } else if (i == getChildCount() - 1 && scrollProgress > 0) { 358 // Over scroll to the right 359 v.setRotationY(-TRANSITION_MAX_ROTATION * scrollProgress); 360 alpha = 1.0f; 361 v.setOverScrollAmount(Math.abs(scrollProgress), false); 362 // On the last page, we don't want the page to have any lateral motion. 363 } else { 364 v.setRotationY(0f); 365 v.setOverScrollAmount(0, false); 366 } 367 } 368 v.setAlpha(alpha); 369 370 // If the view has 0 alpha, we set it to be invisible so as to prevent 371 // it from accepting touches 372 if (alpha == 0) { 373 v.setVisibility(INVISIBLE); 374 } else if (v.getVisibility() != VISIBLE) { 375 v.setVisibility(VISIBLE); 376 } 377 } 378 } 379 } 380 @Override 381 protected void onStartReordering() { 382 super.onStartReordering(); 383 setChildrenOutlineMultiplier(1.0f); 384 showOutlinesAndSidePages(); 385 } 386 387 @Override 388 protected void onEndReordering() { 389 super.onEndReordering(); 390 hideOutlinesAndSidePages(); 391 } 392 393 void showOutlinesAndSidePages() { 394 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel(); 395 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel(); 396 397 PropertyValuesHolder outlinesAlpha = 398 PropertyValuesHolder.ofFloat("childrenOutlineAlpha", 1.0f); 399 PropertyValuesHolder sidePagesAlpha = PropertyValuesHolder.ofFloat("sidePagesAlpha", 1.0f); 400 mChildrenOutlineFadeInAnimation = 401 ObjectAnimator.ofPropertyValuesHolder(this, outlinesAlpha, sidePagesAlpha); 402 403 mChildrenOutlineFadeInAnimation.setDuration(CHILDREN_OUTLINE_FADE_IN_DURATION); 404 mChildrenOutlineFadeInAnimation.start(); 405 } 406 407 public void showInitialPageHints() { 408 // We start with everything showing 409 setChildrenOutlineAlpha(1.0f); 410 setSidePagesAlpha(1.0f); 411 setChildrenOutlineMultiplier(1.0f); 412 413 int currPage = getCurrentPage(); 414 KeyguardWidgetFrame frame = getWidgetPageAt(currPage); 415 frame.setBackgroundAlphaMultiplier(0f); 416 } 417 418 void hideOutlinesAndSidePages() { 419 if (mChildrenOutlineFadeInAnimation != null) mChildrenOutlineFadeInAnimation.cancel(); 420 if (mChildrenOutlineFadeOutAnimation != null) mChildrenOutlineFadeOutAnimation.cancel(); 421 422 PropertyValuesHolder outlinesAlpha = 423 PropertyValuesHolder.ofFloat("childrenOutlineAlpha", 0f); 424 PropertyValuesHolder sidePagesAlpha = PropertyValuesHolder.ofFloat("sidePagesAlpha", 0f); 425 mChildrenOutlineFadeOutAnimation = 426 ObjectAnimator.ofPropertyValuesHolder(this, outlinesAlpha, sidePagesAlpha); 427 428 mChildrenOutlineFadeOutAnimation.setDuration(CHILDREN_OUTLINE_FADE_OUT_DURATION); 429 mChildrenOutlineFadeOutAnimation.setStartDelay(CHILDREN_OUTLINE_FADE_OUT_DELAY); 430 mChildrenOutlineFadeOutAnimation.start(); 431 } 432 433 public void setChildrenOutlineAlpha(float alpha) { 434 mChildrenOutlineAlpha = alpha; 435 for (int i = 0; i < getChildCount(); i++) { 436 getWidgetPageAt(i).setBackgroundAlpha(alpha); 437 } 438 } 439 440 public void setSidePagesAlpha(float alpha) { 441 // This gives the current page, or the destination page if in transit. 442 int curPage = getNextPage(); 443 mSidePagesAlpha = alpha; 444 for (int i = 0; i < getChildCount(); i++) { 445 if (curPage != i) { 446 getWidgetPageAt(i).setContentAlpha(alpha); 447 } else { 448 // We lock the current page alpha to 1. 449 getWidgetPageAt(i).setContentAlpha(1.0f); 450 } 451 } 452 } 453 454 public void setChildrenOutlineMultiplier(float alpha) { 455 mChildrenOutlineAlpha = alpha; 456 for (int i = 0; i < getChildCount(); i++) { 457 getWidgetPageAt(i).setBackgroundAlphaMultiplier(alpha); 458 } 459 } 460 461 public float getSidePagesAlpha() { 462 return mSidePagesAlpha; 463 } 464 465 public float getChildrenOutlineAlpha() { 466 return mChildrenOutlineAlpha; 467 } 468 469 @Override 470 public boolean onLongClick(View v) { 471 if (startReordering()) { 472 return true; 473 } 474 return false; 475 } 476} 477