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 HardwareUiLayout(Context context, AttributeSet attrs)62 public HardwareUiLayout(Context context, AttributeSet attrs) { 63 super(context, attrs); 64 updateSettings(); 65 } 66 67 @Override onAttachedToWindow()68 protected void onAttachedToWindow() { 69 super.onAttachedToWindow(); 70 updateSettings(); 71 Dependency.get(TunerService.class).addTunable(this, EDGE_BLEED, ROUNDED_DIVIDER); 72 getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener); 73 } 74 75 @Override onDetachedFromWindow()76 protected void onDetachedFromWindow() { 77 super.onDetachedFromWindow(); 78 getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener); 79 Dependency.get(TunerService.class).removeTunable(this); 80 } 81 82 @Override onTuningChanged(String key, String newValue)83 public void onTuningChanged(String key, String newValue) { 84 updateSettings(); 85 } 86 updateSettings()87 private void updateSettings() { 88 mEdgeBleed = Settings.Secure.getInt(getContext().getContentResolver(), 89 EDGE_BLEED, 0) != 0; 90 mRoundedDivider = Settings.Secure.getInt(getContext().getContentResolver(), 91 ROUNDED_DIVIDER, 1) != 0; 92 updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding()); 93 mBackground = new HardwareBgDrawable(mRoundedDivider, !mEdgeBleed, getContext()); 94 if (mChild != null) { 95 mChild.setBackground(mBackground); 96 requestLayout(); 97 } 98 } 99 updateEdgeMargin(int edge)100 private void updateEdgeMargin(int edge) { 101 if (mChild != null) { 102 MarginLayoutParams params = (MarginLayoutParams) mChild.getLayoutParams(); 103 if (mRotation == ROTATION_LANDSCAPE) { 104 params.topMargin = edge; 105 } else if (mRotation == ROTATION_SEASCAPE) { 106 params.bottomMargin = edge; 107 } else { 108 params.rightMargin = edge; 109 } 110 mChild.setLayoutParams(params); 111 } 112 } 113 getEdgePadding()114 private int getEdgePadding() { 115 return getContext().getResources().getDimensionPixelSize(R.dimen.edge_margin); 116 } 117 118 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)119 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 120 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 121 if (mChild == null) { 122 if (getChildCount() != 0) { 123 mChild = getChildAt(0); 124 mChild.setBackground(mBackground); 125 updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding()); 126 mOldHeight = mChild.getMeasuredHeight(); 127 mChild.addOnLayoutChangeListener( 128 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> 129 updatePosition()); 130 updateRotation(); 131 } else { 132 return; 133 } 134 } 135 int newHeight = mChild.getMeasuredHeight(); 136 if (newHeight != mOldHeight) { 137 animateChild(mOldHeight, newHeight); 138 } 139 post(() -> updatePosition()); 140 } 141 142 @Override onConfigurationChanged(Configuration newConfig)143 protected void onConfigurationChanged(Configuration newConfig) { 144 super.onConfigurationChanged(newConfig); 145 updateRotation(); 146 } 147 updateRotation()148 private void updateRotation() { 149 int rotation = RotationUtils.getRotation(getContext()); 150 if (rotation != mRotation) { 151 rotate(mRotation, rotation); 152 mRotation = rotation; 153 } 154 } 155 rotate(int from, int to)156 private void rotate(int from, int to) { 157 if (from != ROTATION_NONE && to != ROTATION_NONE) { 158 // Rather than handling this confusing case, just do 2 rotations. 159 rotate(from, ROTATION_NONE); 160 rotate(ROTATION_NONE, to); 161 return; 162 } 163 if (from == ROTATION_LANDSCAPE || to == ROTATION_SEASCAPE) { 164 rotateRight(); 165 } else { 166 rotateLeft(); 167 } 168 if (to != ROTATION_NONE) { 169 if (mChild instanceof LinearLayout) { 170 mRotatedBackground = true; 171 mBackground.setRotatedBackground(true); 172 LinearLayout linearLayout = (LinearLayout) mChild; 173 if (to == ROTATION_SEASCAPE) { 174 swapOrder(linearLayout); 175 } 176 linearLayout.setOrientation(LinearLayout.HORIZONTAL); 177 swapDimens(this.mChild); 178 } 179 } else { 180 if (mChild instanceof LinearLayout) { 181 mRotatedBackground = false; 182 mBackground.setRotatedBackground(false); 183 LinearLayout linearLayout = (LinearLayout) mChild; 184 if (from == ROTATION_SEASCAPE) { 185 swapOrder(linearLayout); 186 } 187 linearLayout.setOrientation(LinearLayout.VERTICAL); 188 swapDimens(mChild); 189 } 190 } 191 } 192 swapOrder(LinearLayout linearLayout)193 private void swapOrder(LinearLayout linearLayout) { 194 ArrayList<View> children = new ArrayList<>(); 195 for (int i = 0; i < linearLayout.getChildCount(); i++) { 196 children.add(0, linearLayout.getChildAt(0)); 197 linearLayout.removeViewAt(0); 198 } 199 children.forEach(v -> linearLayout.addView(v)); 200 } 201 rotateRight()202 private void rotateRight() { 203 rotateRight(this); 204 rotateRight(mChild); 205 swapDimens(this); 206 207 LayoutParams p = (LayoutParams) mChild.getLayoutParams(); 208 p.gravity = rotateGravityRight(p.gravity); 209 mChild.setLayoutParams(p); 210 } 211 swapDimens(View v)212 private void swapDimens(View v) { 213 ViewGroup.LayoutParams params = v.getLayoutParams(); 214 int h = params.width; 215 params.width = params.height; 216 params.height = h; 217 v.setLayoutParams(params); 218 } 219 rotateGravityRight(int gravity)220 private int rotateGravityRight(int gravity) { 221 int retGravity = 0; 222 int layoutDirection = getLayoutDirection(); 223 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); 224 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; 225 226 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 227 case Gravity.CENTER_HORIZONTAL: 228 retGravity |= Gravity.CENTER_VERTICAL; 229 break; 230 case Gravity.RIGHT: 231 retGravity |= Gravity.BOTTOM; 232 break; 233 case Gravity.LEFT: 234 default: 235 retGravity |= Gravity.TOP; 236 break; 237 } 238 239 switch (verticalGravity) { 240 case Gravity.CENTER_VERTICAL: 241 retGravity |= Gravity.CENTER_HORIZONTAL; 242 break; 243 case Gravity.BOTTOM: 244 retGravity |= Gravity.LEFT; 245 break; 246 case Gravity.TOP: 247 default: 248 retGravity |= Gravity.RIGHT; 249 break; 250 } 251 return retGravity; 252 } 253 rotateLeft()254 private void rotateLeft() { 255 rotateLeft(this); 256 rotateLeft(mChild); 257 swapDimens(this); 258 259 LayoutParams p = (LayoutParams) mChild.getLayoutParams(); 260 p.gravity = rotateGravityLeft(p.gravity); 261 mChild.setLayoutParams(p); 262 } 263 rotateGravityLeft(int gravity)264 private int rotateGravityLeft(int gravity) { 265 if (gravity == -1) { 266 gravity = Gravity.TOP | Gravity.START; 267 } 268 int retGravity = 0; 269 int layoutDirection = getLayoutDirection(); 270 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); 271 final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; 272 273 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 274 case Gravity.CENTER_HORIZONTAL: 275 retGravity |= Gravity.CENTER_VERTICAL; 276 break; 277 case Gravity.RIGHT: 278 retGravity |= Gravity.TOP; 279 break; 280 case Gravity.LEFT: 281 default: 282 retGravity |= Gravity.BOTTOM; 283 break; 284 } 285 286 switch (verticalGravity) { 287 case Gravity.CENTER_VERTICAL: 288 retGravity |= Gravity.CENTER_HORIZONTAL; 289 break; 290 case Gravity.BOTTOM: 291 retGravity |= Gravity.RIGHT; 292 break; 293 case Gravity.TOP: 294 default: 295 retGravity |= Gravity.LEFT; 296 break; 297 } 298 return retGravity; 299 } 300 rotateLeft(View v)301 private void rotateLeft(View v) { 302 v.setPadding(v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom(), 303 v.getPaddingLeft()); 304 MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams(); 305 params.setMargins(params.topMargin, params.rightMargin, params.bottomMargin, 306 params.leftMargin); 307 v.setLayoutParams(params); 308 } 309 rotateRight(View v)310 private void rotateRight(View v) { 311 v.setPadding(v.getPaddingBottom(), v.getPaddingLeft(), v.getPaddingTop(), 312 v.getPaddingRight()); 313 MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams(); 314 params.setMargins(params.bottomMargin, params.leftMargin, params.topMargin, 315 params.rightMargin); 316 v.setLayoutParams(params); 317 } 318 319 @Override onLayout(boolean changed, int left, int top, int right, int bottom)320 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 321 super.onLayout(changed, left, top, right, bottom); 322 post(() -> updatePosition()); 323 } 324 animateChild(int oldHeight, int newHeight)325 private void animateChild(int oldHeight, int newHeight) { 326 if (true) return; 327 if (mAnimating) { 328 mAnimation.cancel(); 329 } 330 mAnimating = true; 331 mAnimation = new AnimatorSet(); 332 mAnimation.addListener(new AnimatorListenerAdapter() { 333 @Override 334 public void onAnimationEnd(Animator animation) { 335 mAnimating = false; 336 } 337 }); 338 int fromTop = mChild.getTop(); 339 int fromBottom = mChild.getBottom(); 340 int toTop = fromTop - ((newHeight - oldHeight) / 2); 341 int toBottom = fromBottom + ((newHeight - oldHeight) / 2); 342 ObjectAnimator top = ObjectAnimator.ofInt(mChild, "top", fromTop, toTop); 343 top.addUpdateListener(animation -> mBackground.invalidateSelf()); 344 mAnimation.playTogether(top, 345 ObjectAnimator.ofInt(mChild, "bottom", fromBottom, toBottom)); 346 } 347 setDivisionView(View v)348 public void setDivisionView(View v) { 349 mDivision = v; 350 if (mDivision != null) { 351 mDivision.addOnLayoutChangeListener( 352 (v1, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> 353 updatePosition()); 354 } 355 updatePosition(); 356 } 357 updatePosition()358 private void updatePosition() { 359 if (mChild == null) return; 360 if (mDivision != null && mDivision.getVisibility() == VISIBLE) { 361 int index = mRotatedBackground ? 0 : 1; 362 mDivision.getLocationOnScreen(mTmp2); 363 float trans = mRotatedBackground ? mDivision.getTranslationX() 364 : mDivision.getTranslationY(); 365 int viewTop = (int) (mTmp2[index] + trans); 366 mChild.getLocationOnScreen(mTmp2); 367 viewTop -= mTmp2[index]; 368 setCutPoint(viewTop); 369 } else { 370 setCutPoint(mChild.getMeasuredHeight()); 371 } 372 } 373 setCutPoint(int point)374 private void setCutPoint(int point) { 375 int curPoint = mBackground.getCutPoint(); 376 if (curPoint == point) return; 377 if (getAlpha() == 0 || curPoint == 0) { 378 mBackground.setCutPoint(point); 379 return; 380 } 381 if (mAnimator != null) { 382 if (mEndPoint == point) { 383 return; 384 } 385 mAnimator.cancel(); 386 } 387 mEndPoint = point; 388 mAnimator = ObjectAnimator.ofInt(mBackground, "cutPoint", curPoint, point); 389 if (mCollapse) { 390 mAnimator.setStartDelay(300); 391 mCollapse = false; 392 } 393 mAnimator.start(); 394 } 395 396 @Override getOutlineProvider()397 public ViewOutlineProvider getOutlineProvider() { 398 return super.getOutlineProvider(); 399 } 400 setOutsideTouchListener(OnClickListener onClickListener)401 public void setOutsideTouchListener(OnClickListener onClickListener) { 402 mHasOutsideTouch = true; 403 requestLayout(); 404 setOnClickListener(onClickListener); 405 setClickable(true); 406 setFocusable(true); 407 } 408 setCollapse()409 public void setCollapse() { 410 mCollapse = true; 411 } 412 get(View v)413 public static HardwareUiLayout get(View v) { 414 if (v instanceof HardwareUiLayout) return (HardwareUiLayout) v; 415 if (v.getParent() instanceof View) { 416 return get((View) v.getParent()); 417 } 418 return null; 419 } 420 421 private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = inoutInfo -> { 422 if (mHasOutsideTouch || (mChild == null)) { 423 inoutInfo.setTouchableInsets( 424 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); 425 return; 426 } 427 inoutInfo.setTouchableInsets( 428 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT); 429 inoutInfo.contentInsets.set(mChild.getLeft(), mChild.getTop(), 430 0, getBottom() - mChild.getBottom()); 431 }; 432 } 433