• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
HardwareUiLayout(Context context, AttributeSet attrs)63     public HardwareUiLayout(Context context, AttributeSet attrs) {
64         super(context, attrs);
65         updateSettings();
66     }
67 
68     @Override
onAttachedToWindow()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
onDetachedFromWindow()77     protected void onDetachedFromWindow() {
78         super.onDetachedFromWindow();
79         getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener);
80         Dependency.get(TunerService.class).removeTunable(this);
81     }
82 
83     @Override
onTuningChanged(String key, String newValue)84     public void onTuningChanged(String key, String newValue) {
85         updateSettings();
86     }
87 
updateSettings()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 
updateEdgeMargin(int edge)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 
getEdgePadding()115     private int getEdgePadding() {
116         return getContext().getResources().getDimensionPixelSize(R.dimen.edge_margin);
117     }
118 
119     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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
onConfigurationChanged(Configuration newConfig)144     protected void onConfigurationChanged(Configuration newConfig) {
145         super.onConfigurationChanged(newConfig);
146         updateRotation();
147     }
148 
setSwapOrientation(boolean swapOrientation)149     public void setSwapOrientation(boolean swapOrientation) {
150         mSwapOrientation = swapOrientation;
151     }
152 
updateRotation()153     private void updateRotation() {
154         int rotation = RotationUtils.getRotation(getContext());
155         if (rotation != mRotation) {
156             rotate(mRotation, rotation);
157             mRotation = rotation;
158         }
159     }
160 
rotate(int from, int to)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 
rotateRight()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 
swapDimens(View v)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 
rotateGravityRight(int gravity)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 
rotateLeft()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 
rotateGravityLeft(int gravity)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 
rotateLeft(View v)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 
rotateRight(View v)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
onLayout(boolean changed, int left, int top, int right, int bottom)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 
animateChild(int oldHeight, int newHeight)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 
setDivisionView(View v)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 
updatePosition()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 
setCutPoint(int point)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
getOutlineProvider()391     public ViewOutlineProvider getOutlineProvider() {
392         return super.getOutlineProvider();
393     }
394 
setOutsideTouchListener(OnClickListener onClickListener)395     public void setOutsideTouchListener(OnClickListener onClickListener) {
396         mHasOutsideTouch = true;
397         requestLayout();
398         setOnClickListener(onClickListener);
399         setClickable(true);
400         setFocusable(true);
401     }
402 
setCollapse()403     public void setCollapse() {
404         mCollapse = true;
405     }
406 
get(View v)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