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