• 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 
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