MultiPaneChallengeLayout.java revision c065a5d7271c6683b64578490ccc836c72d3ed78
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 */ 16 17package com.android.internal.policy.impl.keyguard; 18 19import com.android.internal.R; 20 21import android.animation.Animator; 22import android.animation.AnimatorListenerAdapter; 23import android.animation.ObjectAnimator; 24import android.content.Context; 25import android.content.res.Resources; 26import android.content.res.TypedArray; 27import android.graphics.Rect; 28import android.util.AttributeSet; 29import android.util.DisplayMetrics; 30import android.view.Gravity; 31import android.view.View; 32import android.view.ViewGroup; 33import android.widget.LinearLayout; 34 35public class MultiPaneChallengeLayout extends ViewGroup implements ChallengeLayout { 36 private static final String TAG = "MultiPaneChallengeLayout"; 37 38 final int mOrientation; 39 private boolean mIsBouncing; 40 41 public static final int HORIZONTAL = LinearLayout.HORIZONTAL; 42 public static final int VERTICAL = LinearLayout.VERTICAL; 43 protected static final int ANIMATE_BOUNCE_DURATION = 750; 44 45 private KeyguardSecurityContainer mChallengeView; 46 private View mUserSwitcherView; 47 private View mScrimView; 48 private OnBouncerStateChangedListener mBouncerListener; 49 50 private final Rect mTempRect = new Rect(); 51 private final Rect mZeroPadding = new Rect(); 52 53 private final DisplayMetrics mDisplayMetrics; 54 55 private final OnClickListener mScrimClickListener = new OnClickListener() { 56 @Override 57 public void onClick(View v) { 58 hideBouncer(); 59 } 60 }; 61 62 public MultiPaneChallengeLayout(Context context) { 63 this(context, null); 64 } 65 66 public MultiPaneChallengeLayout(Context context, AttributeSet attrs) { 67 this(context, attrs, 0); 68 } 69 70 public MultiPaneChallengeLayout(Context context, AttributeSet attrs, int defStyleAttr) { 71 super(context, attrs, defStyleAttr); 72 73 final TypedArray a = context.obtainStyledAttributes(attrs, 74 R.styleable.MultiPaneChallengeLayout, defStyleAttr, 0); 75 mOrientation = a.getInt(R.styleable.MultiPaneChallengeLayout_orientation, 76 HORIZONTAL); 77 a.recycle(); 78 79 final Resources res = getResources(); 80 mDisplayMetrics = res.getDisplayMetrics(); 81 } 82 83 @Override 84 public boolean isChallengeShowing() { 85 return true; 86 } 87 88 @Override 89 public boolean isChallengeOverlapping() { 90 return false; 91 } 92 93 @Override 94 public void showChallenge(boolean b) { 95 } 96 97 @Override 98 public void showBouncer() { 99 if (mIsBouncing) return; 100 mIsBouncing = true; 101 if (mScrimView != null) { 102 if (mChallengeView != null) { 103 mChallengeView.showBouncer(ANIMATE_BOUNCE_DURATION); 104 } 105 106 Animator anim = ObjectAnimator.ofFloat(mScrimView, "alpha", 1f); 107 anim.setDuration(ANIMATE_BOUNCE_DURATION); 108 anim.addListener(new AnimatorListenerAdapter() { 109 @Override 110 public void onAnimationStart(Animator animation) { 111 mScrimView.setVisibility(VISIBLE); 112 } 113 }); 114 anim.start(); 115 } 116 if (mBouncerListener != null) { 117 mBouncerListener.onBouncerStateChanged(true); 118 } 119 } 120 121 @Override 122 public void hideBouncer() { 123 if (!mIsBouncing) return; 124 mIsBouncing = false; 125 if (mScrimView != null) { 126 if (mChallengeView != null) { 127 mChallengeView.hideBouncer(ANIMATE_BOUNCE_DURATION); 128 } 129 130 Animator anim = ObjectAnimator.ofFloat(mScrimView, "alpha", 0f); 131 anim.setDuration(ANIMATE_BOUNCE_DURATION); 132 anim.addListener(new AnimatorListenerAdapter() { 133 @Override 134 public void onAnimationEnd(Animator animation) { 135 mScrimView.setVisibility(INVISIBLE); 136 } 137 }); 138 anim.start(); 139 } 140 if (mBouncerListener != null) { 141 mBouncerListener.onBouncerStateChanged(false); 142 } 143 } 144 145 @Override 146 public boolean isBouncing() { 147 return mIsBouncing; 148 } 149 150 @Override 151 public void setOnBouncerStateChangedListener(OnBouncerStateChangedListener listener) { 152 mBouncerListener = listener; 153 } 154 155 @Override 156 public void requestChildFocus(View child, View focused) { 157 if (mIsBouncing && child != mChallengeView) { 158 // Clear out of the bouncer if the user tries to move focus outside of 159 // the security challenge view. 160 hideBouncer(); 161 } 162 super.requestChildFocus(child, focused); 163 } 164 165 void setScrimView(View scrim) { 166 if (mScrimView != null) { 167 mScrimView.setOnClickListener(null); 168 } 169 mScrimView = scrim; 170 mScrimView.setAlpha(mIsBouncing ? 1.0f : 0.0f); 171 mScrimView.setVisibility(mIsBouncing ? VISIBLE : INVISIBLE); 172 mScrimView.setFocusable(true); 173 mScrimView.setOnClickListener(mScrimClickListener); 174 } 175 176 private int getVirtualHeight(LayoutParams lp, int height, int heightUsed) { 177 int virtualHeight = height; 178 final View root = getRootView(); 179 if (root != null) { 180 // This calculation is super dodgy and relies on several assumptions. 181 // Specifically that the root of the window will be padded in for insets 182 // and that the window is LAYOUT_IN_SCREEN. 183 virtualHeight = mDisplayMetrics.heightPixels - root.getPaddingTop(); 184 } 185 if (lp.childType == LayoutParams.CHILD_TYPE_WIDGET || 186 lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER) { 187 // Always measure the widget pager/user switcher as if there were no IME insets 188 // on the window. We want to avoid resizing widgets when possible as it can 189 // be ugly/expensive. This lets us simply clip them instead. 190 return virtualHeight - heightUsed; 191 } else if (lp.childType == LayoutParams.CHILD_TYPE_PAGE_DELETE_DROP_TARGET) { 192 return height; 193 } 194 return Math.min(virtualHeight - heightUsed, height); 195 } 196 197 @Override 198 protected void onMeasure(final int widthSpec, final int heightSpec) { 199 if (MeasureSpec.getMode(widthSpec) != MeasureSpec.EXACTLY || 200 MeasureSpec.getMode(heightSpec) != MeasureSpec.EXACTLY) { 201 throw new IllegalArgumentException( 202 "MultiPaneChallengeLayout must be measured with an exact size"); 203 } 204 205 final int width = MeasureSpec.getSize(widthSpec); 206 final int height = MeasureSpec.getSize(heightSpec); 207 setMeasuredDimension(width, height); 208 209 int widthUsed = 0; 210 int heightUsed = 0; 211 212 // First pass. Find the challenge view and measure the user switcher, 213 // which consumes space in the layout. 214 mChallengeView = null; 215 mUserSwitcherView = null; 216 final int count = getChildCount(); 217 for (int i = 0; i < count; i++) { 218 final View child = getChildAt(i); 219 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 220 221 if (lp.childType == LayoutParams.CHILD_TYPE_CHALLENGE) { 222 if (mChallengeView != null) { 223 throw new IllegalStateException( 224 "There may only be one child of type challenge"); 225 } 226 if (!(child instanceof KeyguardSecurityContainer)) { 227 throw new IllegalArgumentException( 228 "Challenge must be a KeyguardSecurityContainer"); 229 } 230 mChallengeView = (KeyguardSecurityContainer) child; 231 } else if (lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER) { 232 if (mUserSwitcherView != null) { 233 throw new IllegalStateException( 234 "There may only be one child of type userSwitcher"); 235 } 236 mUserSwitcherView = child; 237 238 if (child.getVisibility() == GONE) continue; 239 240 int adjustedWidthSpec = widthSpec; 241 int adjustedHeightSpec = heightSpec; 242 if (lp.maxWidth >= 0) { 243 adjustedWidthSpec = MeasureSpec.makeMeasureSpec( 244 Math.min(lp.maxWidth, width), MeasureSpec.EXACTLY); 245 } 246 if (lp.maxHeight >= 0) { 247 adjustedHeightSpec = MeasureSpec.makeMeasureSpec( 248 Math.min(lp.maxHeight, height), MeasureSpec.EXACTLY); 249 } 250 // measureChildWithMargins will resolve layout direction for the LayoutParams 251 measureChildWithMargins(child, adjustedWidthSpec, 0, adjustedHeightSpec, 0); 252 253 // Only subtract out space from one dimension. Favor vertical. 254 // Offset by 1.5x to add some balance along the other edge. 255 if (Gravity.isVertical(lp.gravity)) { 256 heightUsed += child.getMeasuredHeight() * 1.5f; 257 } else if (Gravity.isHorizontal(lp.gravity)) { 258 widthUsed += child.getMeasuredWidth() * 1.5f; 259 } 260 } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) { 261 setScrimView(child); 262 child.measure(widthSpec, heightSpec); 263 } 264 } 265 266 // Second pass. Measure everything that's left. 267 for (int i = 0; i < count; i++) { 268 final View child = getChildAt(i); 269 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 270 271 if (lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER || 272 lp.childType == LayoutParams.CHILD_TYPE_SCRIM || 273 child.getVisibility() == GONE) { 274 // Don't need to measure GONE children, and the user switcher was already measured. 275 continue; 276 } 277 278 final int virtualHeight = getVirtualHeight(lp, height, heightUsed); 279 280 int adjustedWidthSpec; 281 int adjustedHeightSpec; 282 if (lp.centerWithinArea > 0) { 283 if (mOrientation == HORIZONTAL) { 284 adjustedWidthSpec = MeasureSpec.makeMeasureSpec( 285 (int) ((width - widthUsed) * lp.centerWithinArea + 0.5f), 286 MeasureSpec.EXACTLY); 287 adjustedHeightSpec = MeasureSpec.makeMeasureSpec( 288 virtualHeight, MeasureSpec.EXACTLY); 289 } else { 290 adjustedWidthSpec = MeasureSpec.makeMeasureSpec( 291 width - widthUsed, MeasureSpec.EXACTLY); 292 adjustedHeightSpec = MeasureSpec.makeMeasureSpec( 293 (int) (virtualHeight * lp.centerWithinArea + 0.5f), 294 MeasureSpec.EXACTLY); 295 } 296 } else { 297 adjustedWidthSpec = MeasureSpec.makeMeasureSpec( 298 width - widthUsed, MeasureSpec.EXACTLY); 299 adjustedHeightSpec = MeasureSpec.makeMeasureSpec( 300 virtualHeight, MeasureSpec.EXACTLY); 301 } 302 if (lp.maxWidth >= 0) { 303 adjustedWidthSpec = MeasureSpec.makeMeasureSpec( 304 Math.min(lp.maxWidth, MeasureSpec.getSize(adjustedWidthSpec)), 305 MeasureSpec.EXACTLY); 306 } 307 if (lp.maxHeight >= 0) { 308 adjustedHeightSpec = MeasureSpec.makeMeasureSpec( 309 Math.min(lp.maxHeight, MeasureSpec.getSize(adjustedHeightSpec)), 310 MeasureSpec.EXACTLY); 311 } 312 313 measureChildWithMargins(child, adjustedWidthSpec, 0, adjustedHeightSpec, 0); 314 } 315 } 316 317 @Override 318 protected void onLayout(boolean changed, int l, int t, int r, int b) { 319 final Rect padding = mTempRect; 320 padding.left = getPaddingLeft(); 321 padding.top = getPaddingTop(); 322 padding.right = getPaddingRight(); 323 padding.bottom = getPaddingBottom(); 324 final int width = r - l; 325 final int height = b - t; 326 327 // Reserve extra space in layout for the user switcher by modifying 328 // local padding during this layout pass 329 if (mUserSwitcherView != null && mUserSwitcherView.getVisibility() != GONE) { 330 layoutWithGravity(width, height, mUserSwitcherView, padding, true); 331 } 332 333 final int count = getChildCount(); 334 for (int i = 0; i < count; i++) { 335 final View child = getChildAt(i); 336 LayoutParams lp = (LayoutParams) child.getLayoutParams(); 337 338 // We did the user switcher above if we have one. 339 if (child == mUserSwitcherView || child.getVisibility() == GONE) continue; 340 341 if (child == mScrimView) { 342 child.layout(0, 0, width, height); 343 continue; 344 } else if (lp.childType == LayoutParams.CHILD_TYPE_PAGE_DELETE_DROP_TARGET) { 345 layoutWithGravity(width, height, child, mZeroPadding, false); 346 continue; 347 } 348 349 layoutWithGravity(width, height, child, padding, false); 350 } 351 } 352 353 private void layoutWithGravity(int width, int height, View child, Rect padding, 354 boolean adjustPadding) { 355 final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 356 357 final int heightUsed = padding.top + padding.bottom - getPaddingTop() - getPaddingBottom(); 358 height = getVirtualHeight(lp, height, heightUsed); 359 360 final int gravity = Gravity.getAbsoluteGravity(lp.gravity, getLayoutDirection()); 361 362 final boolean fixedLayoutSize = lp.centerWithinArea > 0; 363 final boolean fixedLayoutHorizontal = fixedLayoutSize && mOrientation == HORIZONTAL; 364 final boolean fixedLayoutVertical = fixedLayoutSize && mOrientation == VERTICAL; 365 366 final int adjustedWidth; 367 final int adjustedHeight; 368 if (fixedLayoutHorizontal) { 369 final int paddedWidth = width - padding.left - padding.right; 370 adjustedWidth = (int) (paddedWidth * lp.centerWithinArea + 0.5f); 371 adjustedHeight = height; 372 } else if (fixedLayoutVertical) { 373 final int paddedHeight = height - padding.top - padding.bottom; 374 adjustedWidth = width; 375 adjustedHeight = (int) (paddedHeight * lp.centerWithinArea + 0.5f); 376 } else { 377 adjustedWidth = width; 378 adjustedHeight = height; 379 } 380 381 final boolean isVertical = Gravity.isVertical(gravity); 382 final boolean isHorizontal = Gravity.isHorizontal(gravity); 383 final int childWidth = child.getMeasuredWidth(); 384 final int childHeight = child.getMeasuredHeight(); 385 386 int left = padding.left; 387 int top = padding.top; 388 int right = left + childWidth; 389 int bottom = top + childHeight; 390 switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) { 391 case Gravity.TOP: 392 top = fixedLayoutVertical ? 393 padding.top + (adjustedHeight - childHeight) / 2 : padding.top; 394 bottom = top + childHeight; 395 if (adjustPadding && isVertical) { 396 padding.top = bottom; 397 padding.bottom += childHeight / 2; 398 } 399 break; 400 case Gravity.BOTTOM: 401 bottom = fixedLayoutVertical 402 ? padding.top + height - (adjustedHeight - childHeight) / 2 403 : padding.top + height; 404 top = bottom - childHeight; 405 if (adjustPadding && isVertical) { 406 padding.bottom = height - top; 407 padding.top += childHeight / 2; 408 } 409 break; 410 case Gravity.CENTER_VERTICAL: 411 top = padding.top + (height - childHeight) / 2; 412 bottom = top + childHeight; 413 break; 414 } 415 switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 416 case Gravity.LEFT: 417 left = fixedLayoutHorizontal ? 418 padding.left + (adjustedWidth - childWidth) / 2 : padding.left; 419 right = left + childWidth; 420 if (adjustPadding && isHorizontal && !isVertical) { 421 padding.left = right; 422 padding.right += childWidth / 2; 423 } 424 break; 425 case Gravity.RIGHT: 426 right = fixedLayoutHorizontal 427 ? width - padding.right - (adjustedWidth - childWidth) / 2 428 : width - padding.right; 429 left = right - childWidth; 430 if (adjustPadding && isHorizontal && !isVertical) { 431 padding.right = width - left; 432 padding.left += childWidth / 2; 433 } 434 break; 435 case Gravity.CENTER_HORIZONTAL: 436 final int paddedWidth = width - padding.left - padding.right; 437 left = (paddedWidth - childWidth) / 2; 438 right = left + childWidth; 439 break; 440 } 441 child.layout(left, top, right, bottom); 442 } 443 444 @Override 445 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 446 return new LayoutParams(getContext(), attrs, this); 447 } 448 449 @Override 450 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 451 return p instanceof LayoutParams ? new LayoutParams((LayoutParams) p) : 452 p instanceof MarginLayoutParams ? new LayoutParams((MarginLayoutParams) p) : 453 new LayoutParams(p); 454 } 455 456 @Override 457 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 458 return new LayoutParams(); 459 } 460 461 @Override 462 protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 463 return p instanceof LayoutParams; 464 } 465 466 public static class LayoutParams extends MarginLayoutParams { 467 468 public float centerWithinArea = 0; 469 470 public int childType = 0; 471 472 public static final int CHILD_TYPE_NONE = 0; 473 public static final int CHILD_TYPE_WIDGET = 1; 474 public static final int CHILD_TYPE_CHALLENGE = 2; 475 public static final int CHILD_TYPE_USER_SWITCHER = 3; 476 public static final int CHILD_TYPE_SCRIM = 4; 477 public static final int CHILD_TYPE_PAGE_DELETE_DROP_TARGET = 7; 478 479 public int gravity = Gravity.NO_GRAVITY; 480 481 public int maxWidth = -1; 482 public int maxHeight = -1; 483 484 public LayoutParams() { 485 this(WRAP_CONTENT, WRAP_CONTENT); 486 } 487 488 LayoutParams(Context c, AttributeSet attrs, MultiPaneChallengeLayout parent) { 489 super(c, attrs); 490 491 final TypedArray a = c.obtainStyledAttributes(attrs, 492 R.styleable.MultiPaneChallengeLayout_Layout); 493 494 centerWithinArea = a.getFloat( 495 R.styleable.MultiPaneChallengeLayout_Layout_layout_centerWithinArea, 0); 496 childType = a.getInt(R.styleable.MultiPaneChallengeLayout_Layout_layout_childType, 497 CHILD_TYPE_NONE); 498 gravity = a.getInt(R.styleable.MultiPaneChallengeLayout_Layout_layout_gravity, 499 Gravity.NO_GRAVITY); 500 maxWidth = a.getDimensionPixelSize( 501 R.styleable.MultiPaneChallengeLayout_Layout_layout_maxWidth, -1); 502 maxHeight = a.getDimensionPixelSize( 503 R.styleable.MultiPaneChallengeLayout_Layout_layout_maxHeight, -1); 504 505 // Default gravity settings based on type and parent orientation 506 if (gravity == Gravity.NO_GRAVITY) { 507 if (parent.mOrientation == HORIZONTAL) { 508 switch (childType) { 509 case CHILD_TYPE_WIDGET: 510 gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL; 511 break; 512 case CHILD_TYPE_CHALLENGE: 513 gravity = Gravity.RIGHT | Gravity.CENTER_VERTICAL; 514 break; 515 case CHILD_TYPE_USER_SWITCHER: 516 gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; 517 break; 518 } 519 } else { 520 switch (childType) { 521 case CHILD_TYPE_WIDGET: 522 gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; 523 break; 524 case CHILD_TYPE_CHALLENGE: 525 gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; 526 break; 527 case CHILD_TYPE_USER_SWITCHER: 528 gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; 529 break; 530 } 531 } 532 } 533 534 a.recycle(); 535 } 536 537 public LayoutParams(int width, int height) { 538 super(width, height); 539 } 540 541 public LayoutParams(ViewGroup.LayoutParams source) { 542 super(source); 543 } 544 545 public LayoutParams(MarginLayoutParams source) { 546 super(source); 547 } 548 549 public LayoutParams(LayoutParams source) { 550 this((MarginLayoutParams) source); 551 552 centerWithinArea = source.centerWithinArea; 553 childType = source.childType; 554 gravity = source.gravity; 555 maxWidth = source.maxWidth; 556 maxHeight = source.maxHeight; 557 } 558 } 559} 560