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