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