/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.android.systemui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.Configuration; import android.provider.Settings; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.widget.FrameLayout; import android.widget.LinearLayout; import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.leak.RotationUtils; import java.util.ArrayList; import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE; import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE; import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE; public class HardwareUiLayout extends FrameLayout implements Tunable { private static final String EDGE_BLEED = "sysui_hwui_edge_bleed"; private static final String ROUNDED_DIVIDER = "sysui_hwui_rounded_divider"; private final int[] mTmp2 = new int[2]; private View mChild; private int mOldHeight; private boolean mAnimating; private AnimatorSet mAnimation; private View mDivision; private boolean mHasOutsideTouch; private HardwareBgDrawable mBackground; private Animator mAnimator; private boolean mCollapse; private int mEndPoint; private boolean mEdgeBleed; private boolean mRoundedDivider; private int mRotation = ROTATION_NONE; private boolean mRotatedBackground; private boolean mSwapOrientation = true; public HardwareUiLayout(Context context, AttributeSet attrs) { super(context, attrs); updateSettings(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); updateSettings(); Dependency.get(TunerService.class).addTunable(this, EDGE_BLEED, ROUNDED_DIVIDER); getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener); Dependency.get(TunerService.class).removeTunable(this); } @Override public void onTuningChanged(String key, String newValue) { updateSettings(); } private void updateSettings() { mEdgeBleed = Settings.Secure.getInt(getContext().getContentResolver(), EDGE_BLEED, 0) != 0; mRoundedDivider = Settings.Secure.getInt(getContext().getContentResolver(), ROUNDED_DIVIDER, 1) != 0; updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding()); mBackground = new HardwareBgDrawable(mRoundedDivider, !mEdgeBleed, getContext()); if (mChild != null) { mChild.setBackground(mBackground); requestLayout(); } } private void updateEdgeMargin(int edge) { if (mChild != null) { MarginLayoutParams params = (MarginLayoutParams) mChild.getLayoutParams(); if (mRotation == ROTATION_LANDSCAPE) { params.topMargin = edge; } else if (mRotation == ROTATION_SEASCAPE) { params.bottomMargin = edge; } else { params.rightMargin = edge; } mChild.setLayoutParams(params); } } private int getEdgePadding() { return getContext().getResources().getDimensionPixelSize(R.dimen.edge_margin); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mChild == null) { if (getChildCount() != 0) { mChild = getChildAt(0); mChild.setBackground(mBackground); updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding()); mOldHeight = mChild.getMeasuredHeight(); mChild.addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> updatePosition()); updateRotation(); } else { return; } } int newHeight = mChild.getMeasuredHeight(); if (newHeight != mOldHeight) { animateChild(mOldHeight, newHeight); } post(() -> updatePosition()); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); updateRotation(); } public void setSwapOrientation(boolean swapOrientation) { mSwapOrientation = swapOrientation; } private void updateRotation() { int rotation = RotationUtils.getRotation(getContext()); if (rotation != mRotation) { rotate(mRotation, rotation); mRotation = rotation; } } private void rotate(int from, int to) { if (from != ROTATION_NONE && to != ROTATION_NONE) { // Rather than handling this confusing case, just do 2 rotations. rotate(from, ROTATION_NONE); rotate(ROTATION_NONE, to); return; } if (from == ROTATION_LANDSCAPE || to == ROTATION_SEASCAPE) { rotateRight(); } else { rotateLeft(); } if (to != ROTATION_NONE) { if (mChild instanceof LinearLayout) { mRotatedBackground = true; mBackground.setRotatedBackground(true); LinearLayout linearLayout = (LinearLayout) mChild; if (to == ROTATION_SEASCAPE) { swapOrder(linearLayout); } if (mSwapOrientation) { linearLayout.setOrientation(LinearLayout.HORIZONTAL); } swapDimens(this.mChild); } } else { if (mChild instanceof LinearLayout) { mRotatedBackground = false; mBackground.setRotatedBackground(false); LinearLayout linearLayout = (LinearLayout) mChild; if (from == ROTATION_SEASCAPE) { swapOrder(linearLayout); } if (mSwapOrientation) { linearLayout.setOrientation(LinearLayout.VERTICAL); } swapDimens(mChild); } } } private void swapOrder(LinearLayout linearLayout) { ArrayList children = new ArrayList<>(); for (int i = 0; i < linearLayout.getChildCount(); i++) { children.add(0, linearLayout.getChildAt(0)); linearLayout.removeViewAt(0); } children.forEach(v -> linearLayout.addView(v)); } private void rotateRight() { rotateRight(this); rotateRight(mChild); swapDimens(this); LayoutParams p = (LayoutParams) mChild.getLayoutParams(); p.gravity = rotateGravityRight(p.gravity); mChild.setLayoutParams(p); } private void swapDimens(View v) { ViewGroup.LayoutParams params = v.getLayoutParams(); int h = params.width; params.width = params.height; params.height = h; v.setLayoutParams(params); } private int rotateGravityRight(int gravity) { int retGravity = 0; int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: retGravity |= Gravity.CENTER_VERTICAL; break; case Gravity.RIGHT: retGravity |= Gravity.BOTTOM; break; case Gravity.LEFT: default: retGravity |= Gravity.TOP; break; } switch (verticalGravity) { case Gravity.CENTER_VERTICAL: retGravity |= Gravity.CENTER_HORIZONTAL; break; case Gravity.BOTTOM: retGravity |= Gravity.LEFT; break; case Gravity.TOP: default: retGravity |= Gravity.RIGHT; break; } return retGravity; } private void rotateLeft() { rotateLeft(this); rotateLeft(mChild); swapDimens(this); LayoutParams p = (LayoutParams) mChild.getLayoutParams(); p.gravity = rotateGravityLeft(p.gravity); mChild.setLayoutParams(p); } private int rotateGravityLeft(int gravity) { if (gravity == -1) { gravity = Gravity.TOP | Gravity.START; } int retGravity = 0; int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: retGravity |= Gravity.CENTER_VERTICAL; break; case Gravity.RIGHT: retGravity |= Gravity.TOP; break; case Gravity.LEFT: default: retGravity |= Gravity.BOTTOM; break; } switch (verticalGravity) { case Gravity.CENTER_VERTICAL: retGravity |= Gravity.CENTER_HORIZONTAL; break; case Gravity.BOTTOM: retGravity |= Gravity.RIGHT; break; case Gravity.TOP: default: retGravity |= Gravity.LEFT; break; } return retGravity; } private void rotateLeft(View v) { v.setPadding(v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom(), v.getPaddingLeft()); MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams(); params.setMargins(params.topMargin, params.rightMargin, params.bottomMargin, params.leftMargin); v.setLayoutParams(params); } private void rotateRight(View v) { v.setPadding(v.getPaddingBottom(), v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight()); MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams(); params.setMargins(params.bottomMargin, params.leftMargin, params.topMargin, params.rightMargin); v.setLayoutParams(params); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); post(() -> updatePosition()); } private void animateChild(int oldHeight, int newHeight) { if (true) return; if (mAnimating) { mAnimation.cancel(); } mAnimating = true; mAnimation = new AnimatorSet(); mAnimation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { mAnimating = false; } }); int fromTop = mChild.getTop(); int fromBottom = mChild.getBottom(); int toTop = fromTop - ((newHeight - oldHeight) / 2); int toBottom = fromBottom + ((newHeight - oldHeight) / 2); ObjectAnimator top = ObjectAnimator.ofInt(mChild, "top", fromTop, toTop); top.addUpdateListener(animation -> mBackground.invalidateSelf()); mAnimation.playTogether(top, ObjectAnimator.ofInt(mChild, "bottom", fromBottom, toBottom)); } public void setDivisionView(View v) { mDivision = v; if (mDivision != null) { mDivision.addOnLayoutChangeListener( (v1, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> updatePosition()); } updatePosition(); } private void updatePosition() { if (mChild == null) return; if (mDivision != null && mDivision.getVisibility() == VISIBLE) { int index = mRotatedBackground ? 0 : 1; mDivision.getLocationOnScreen(mTmp2); float trans = mRotatedBackground ? mDivision.getTranslationX() : mDivision.getTranslationY(); int viewTop = (int) (mTmp2[index] + trans); mChild.getLocationOnScreen(mTmp2); viewTop -= mTmp2[index]; setCutPoint(viewTop); } else { setCutPoint(mChild.getMeasuredHeight()); } } private void setCutPoint(int point) { int curPoint = mBackground.getCutPoint(); if (curPoint == point) return; if (getAlpha() == 0 || curPoint == 0) { mBackground.setCutPoint(point); return; } if (mAnimator != null) { if (mEndPoint == point) { return; } mAnimator.cancel(); } mEndPoint = point; mAnimator = ObjectAnimator.ofInt(mBackground, "cutPoint", curPoint, point); if (mCollapse) { mAnimator.setStartDelay(300); mCollapse = false; } mAnimator.start(); } @Override public ViewOutlineProvider getOutlineProvider() { return super.getOutlineProvider(); } public void setOutsideTouchListener(OnClickListener onClickListener) { mHasOutsideTouch = true; requestLayout(); setOnClickListener(onClickListener); setClickable(true); setFocusable(true); } public void setCollapse() { mCollapse = true; } public static HardwareUiLayout get(View v) { if (v instanceof HardwareUiLayout) return (HardwareUiLayout) v; if (v.getParent() instanceof View) { return get((View) v.getParent()); } return null; } private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = inoutInfo -> { if (mHasOutsideTouch || (mChild == null)) { inoutInfo.setTouchableInsets( ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); return; } inoutInfo.setTouchableInsets( ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT); inoutInfo.contentInsets.set(mChild.getLeft(), mChild.getTop(), 0, getBottom() - mChild.getBottom()); }; }