1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.systemui; 16 17 import android.animation.Animator; 18 import android.animation.AnimatorListenerAdapter; 19 import android.animation.AnimatorSet; 20 import android.animation.ObjectAnimator; 21 import android.content.Context; 22 import android.content.res.Configuration; 23 import android.provider.Settings; 24 import android.util.AttributeSet; 25 import android.view.Gravity; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.view.ViewOutlineProvider; 29 import android.view.ViewTreeObserver; 30 import android.widget.FrameLayout; 31 import android.widget.LinearLayout; 32 import com.android.systemui.tuner.TunerService; 33 import com.android.systemui.tuner.TunerService.Tunable; 34 import com.android.systemui.util.leak.RotationUtils; 35 36 import java.util.ArrayList; 37 38 import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; 39 import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; 40 import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; 41 42 public class HardwareUiLayout extends FrameLayout implements Tunable { 43 44 private static final String EDGE_BLEED = "sysui_hwui_edge_bleed"; 45 private static final String ROUNDED_DIVIDER = "sysui_hwui_rounded_divider"; 46 private final int[] mTmp2 = new int[2]; 47 private View mChild; 48 private int mOldHeight; 49 private boolean mAnimating; 50 private AnimatorSet mAnimation; 51 private View mDivision; 52 private boolean mHasOutsideTouch; 53 private HardwareBgDrawable mBackground; 54 private Animator mAnimator; 55 private boolean mCollapse; 56 private int mEndPoint; 57 private boolean mEdgeBleed; 58 private boolean mRoundedDivider; 59 private int mRotation = ROTATION_NONE; 60 private boolean mRotatedBackground; 61 private boolean mSwapOrientation = true; 62 63 public HardwareUiLayout(Context context, AttributeSet attrs) { 64 super(context, attrs); 65 updateSettings(); 66 } 67 68 @Override 69 protected void onAttachedToWindow() { 70 super.onAttachedToWindow(); 71 updateSettings(); 72 Dependency.get(TunerService.class).addTunable(this, EDGE_BLEED, ROUNDED_DIVIDER); 73 getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener); 74 } 75 76 @Override 77 protected void onDetachedFromWindow() { 78 super.onDetachedFromWindow(); 79 getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener); 80 Dependency.get(TunerService.class).removeTunable(this); 81 } 82 83 @Override 84 public void onTuningChanged(String key, String newValue) { 85 updateSettings(); 86 } 87 88 private void updateSettings() { 89 mEdgeBleed = Settings.Secure.getInt(getContext().getContentResolver(), 90 EDGE_BLEED, 0) != 0; 91 mRoundedDivider = Settings.Secure.getInt(getContext().getContentResolver(), 92 ROUNDED_DIVIDER, 0) != 0; 93 updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding()); 94 mBackground = new HardwareBgDrawable(mRoundedDivider, !mEdgeBleed, getContext()); 95 if (mChild != null) { 96 mChild.setBackground(mBackground); 97 requestLayout(); 98 } 99 } 100 101 private void updateEdgeMargin(int edge) { 102 if (mChild != null) { 103 MarginLayoutParams params = (MarginLayoutParams) mChild.getLayoutParams(); 104 if (mRotation == ROTATION_LANDSCAPE) { 105 params.topMargin = edge; 106 } else if (mRotation == ROTATION_SEASCAPE) { 107 params.bottomMargin = edge; 108 } else { 109 params.rightMargin = edge; 110 } 111 mChild.setLayoutParams(params); 112 } 113 } 114 115 private int getEdgePadding() { 116 return getContext().getResources().getDimensionPixelSize(R.dimen.edge_margin); 117 } 118 119 @Override 120 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 121 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 122 if (mChild == null) { 123 if (getChildCount() != 0) { 124 mChild = getChildAt(0); 125 mChild.setBackground(mBackground); 126 updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding()); 127 mOldHeight = mChild.getMeasuredHeight(); 128 mChild.addOnLayoutChangeListener( 129 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> 130 updatePosition()); 131 updateRotation(); 132 } else { 133 return; 134 } 135 } 136 int newHeight = mChild.getMeasuredHeight(); 137 if (newHeight != mOldHeight) { 138 animateChild(mOldHeight, newHeight); 139 } 140 post(() -> updatePosition()); 141 } 142 143 @Override 144 protected void onConfigurationChanged(Configuration newConfig) { 145 super.onConfigurationChanged(newConfig); 146 updateRotation(); 147 } 148 149 public void setSwapOrientation(boolean swapOrientation) { 150 mSwapOrientation = swapOrientation; 151 } 152 153 private void updateRotation() { 154 int rotation = RotationUtils.getRotation(getContext()); 155 if (rotation != mRotation) { 156 rotate(mRotation, rotation); 157 mRotation = rotation; 158 } 159 } 160 161 private void rotate(int from, int to) { 162 if (from != ROTATION_NONE && to != ROTATION_NONE) { 163 // Rather than handling this confusing case, just do 2 rotations. 164 rotate(from, ROTATION_NONE); 165 rotate(ROTATION_NONE, to); 166 return; 167 } 168 if (from == ROTATION_LANDSCAPE || to == ROTATION_SEASCAPE) { 169 rotateRight(); 170 } else { 171 rotateLeft(); 172 } 173 if (to != ROTATION_NONE) { 174 if (mChild instanceof LinearLayout) { 175 mRotatedBackground = true; 176 mBackground.setRotatedBackground(true); 177 LinearLayout linearLayout = (LinearLayout) mChild; 178 if (mSwapOrientation) { 179 linearLayout.setOrientation(LinearLayout.HORIZONTAL); 180 } 181 swapDimens(this.mChild); 182 } 183 } else { 184 if (mChild instanceof LinearLayout) { 185 mRotatedBackground = false; 186 mBackground.setRotatedBackground(false); 187 LinearLayout linearLayout = (LinearLayout) mChild; 188 if (mSwapOrientation) { 189 linearLayout.setOrientation(LinearLayout.VERTICAL); 190 } 191 swapDimens(mChild); 192 } 193 } 194 } 195 196 private void rotateRight() { 197 rotateRight(this); 198 rotateRight(mChild); 199 swapDimens(this); 200 201 LayoutParams p = (LayoutParams) mChild.getLayoutParams(); 202 p.gravity = rotateGravityRight(p.gravity); 203 mChild.setLayoutParams(p); 204 } 205 206 private void swapDimens(View v) { 207 ViewGroup.LayoutParams params = v.getLayoutParams(); 208 int h = params.width; 209 params.width = params.height; 210 params.height = h; 211 v.setLayoutParams(params); 212 } 213 214 private int rotateGravityRight(int gravity) { 215 int retGravity = 0; 216 int layoutDirection = getLayoutDirection(); 217 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); 218 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; 219 220 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 221 case Gravity.CENTER_HORIZONTAL: 222 retGravity |= Gravity.CENTER_VERTICAL; 223 break; 224 case Gravity.RIGHT: 225 retGravity |= Gravity.BOTTOM; 226 break; 227 case Gravity.LEFT: 228 default: 229 retGravity |= Gravity.TOP; 230 break; 231 } 232 233 switch (verticalGravity) { 234 case Gravity.CENTER_VERTICAL: 235 retGravity |= Gravity.CENTER_HORIZONTAL; 236 break; 237 case Gravity.BOTTOM: 238 retGravity |= Gravity.LEFT; 239 break; 240 case Gravity.TOP: 241 default: 242 retGravity |= Gravity.RIGHT; 243 break; 244 } 245 return retGravity; 246 } 247 248 private void rotateLeft() { 249 rotateLeft(this); 250 rotateLeft(mChild); 251 swapDimens(this); 252 253 LayoutParams p = (LayoutParams) mChild.getLayoutParams(); 254 p.gravity = rotateGravityLeft(p.gravity); 255 mChild.setLayoutParams(p); 256 } 257 258 private int rotateGravityLeft(int gravity) { 259 if (gravity == -1) { 260 gravity = Gravity.TOP | Gravity.START; 261 } 262 int retGravity = 0; 263 int layoutDirection = getLayoutDirection(); 264 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); 265 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; 266 267 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 268 case Gravity.CENTER_HORIZONTAL: 269 retGravity |= Gravity.CENTER_VERTICAL; 270 break; 271 case Gravity.RIGHT: 272 retGravity |= Gravity.TOP; 273 break; 274 case Gravity.LEFT: 275 default: 276 retGravity |= Gravity.BOTTOM; 277 break; 278 } 279 280 switch (verticalGravity) { 281 case Gravity.CENTER_VERTICAL: 282 retGravity |= Gravity.CENTER_HORIZONTAL; 283 break; 284 case Gravity.BOTTOM: 285 retGravity |= Gravity.RIGHT; 286 break; 287 case Gravity.TOP: 288 default: 289 retGravity |= Gravity.LEFT; 290 break; 291 } 292 return retGravity; 293 } 294 295 private void rotateLeft(View v) { 296 v.setPadding(v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom(), 297 v.getPaddingLeft()); 298 MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams(); 299 params.setMargins(params.topMargin, params.rightMargin, params.bottomMargin, 300 params.leftMargin); 301 v.setLayoutParams(params); 302 } 303 304 private void rotateRight(View v) { 305 v.setPadding(v.getPaddingBottom(), v.getPaddingLeft(), v.getPaddingTop(), 306 v.getPaddingRight()); 307 MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams(); 308 params.setMargins(params.bottomMargin, params.leftMargin, params.topMargin, 309 params.rightMargin); 310 v.setLayoutParams(params); 311 } 312 313 @Override 314 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 315 super.onLayout(changed, left, top, right, bottom); 316 post(() -> updatePosition()); 317 } 318 319 private void animateChild(int oldHeight, int newHeight) { 320 if (true) return; 321 if (mAnimating) { 322 mAnimation.cancel(); 323 } 324 mAnimating = true; 325 mAnimation = new AnimatorSet(); 326 mAnimation.addListener(new AnimatorListenerAdapter() { 327 @Override 328 public void onAnimationEnd(Animator animation) { 329 mAnimating = false; 330 } 331 }); 332 int fromTop = mChild.getTop(); 333 int fromBottom = mChild.getBottom(); 334 int toTop = fromTop - ((newHeight - oldHeight) / 2); 335 int toBottom = fromBottom + ((newHeight - oldHeight) / 2); 336 ObjectAnimator top = ObjectAnimator.ofInt(mChild, "top", fromTop, toTop); 337 top.addUpdateListener(animation -> mBackground.invalidateSelf()); 338 mAnimation.playTogether(top, 339 ObjectAnimator.ofInt(mChild, "bottom", fromBottom, toBottom)); 340 } 341 342 public void setDivisionView(View v) { 343 mDivision = v; 344 if (mDivision != null) { 345 mDivision.addOnLayoutChangeListener( 346 (v1, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> 347 updatePosition()); 348 } 349 updatePosition(); 350 } 351 352 private void updatePosition() { 353 if (mChild == null) return; 354 if (mDivision != null && mDivision.getVisibility() == VISIBLE) { 355 int index = mRotatedBackground ? 0 : 1; 356 mDivision.getLocationOnScreen(mTmp2); 357 float trans = mRotatedBackground ? mDivision.getTranslationX() 358 : mDivision.getTranslationY(); 359 int viewTop = (int) (mTmp2[index] + trans); 360 mChild.getLocationOnScreen(mTmp2); 361 viewTop -= mTmp2[index]; 362 setCutPoint(viewTop); 363 } else { 364 setCutPoint(mChild.getMeasuredHeight()); 365 } 366 } 367 368 private void setCutPoint(int point) { 369 int curPoint = mBackground.getCutPoint(); 370 if (curPoint == point) return; 371 if (getAlpha() == 0 || curPoint == 0) { 372 mBackground.setCutPoint(point); 373 return; 374 } 375 if (mAnimator != null) { 376 if (mEndPoint == point) { 377 return; 378 } 379 mAnimator.cancel(); 380 } 381 mEndPoint = point; 382 mAnimator = ObjectAnimator.ofInt(mBackground, "cutPoint", curPoint, point); 383 if (mCollapse) { 384 mAnimator.setStartDelay(300); 385 mCollapse = false; 386 } 387 mAnimator.start(); 388 } 389 390 @Override 391 public ViewOutlineProvider getOutlineProvider() { 392 return super.getOutlineProvider(); 393 } 394 395 public void setOutsideTouchListener(OnClickListener onClickListener) { 396 mHasOutsideTouch = true; 397 requestLayout(); 398 setOnClickListener(onClickListener); 399 setClickable(true); 400 setFocusable(true); 401 } 402 403 public void setCollapse() { 404 mCollapse = true; 405 } 406 407 public static HardwareUiLayout get(View v) { 408 if (v instanceof HardwareUiLayout) return (HardwareUiLayout) v; 409 if (v.getParent() instanceof View) { 410 return get((View) v.getParent()); 411 } 412 return null; 413 } 414 415 private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = inoutInfo -> { 416 if (mHasOutsideTouch || (mChild == null)) { 417 inoutInfo.setTouchableInsets( 418 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); 419 return; 420 } 421 inoutInfo.setTouchableInsets( 422 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT); 423 inoutInfo.contentInsets.set(mChild.getLeft(), mChild.getTop(), 424 0, getBottom() - mChild.getBottom()); 425 }; 426 } 427