Home | History | Annotate | Download | only in systemui
      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