• 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 static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
18 import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
19 import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.animation.AnimatorSet;
24 import android.animation.ObjectAnimator;
25 import android.content.Context;
26 import android.provider.Settings;
27 import android.util.AttributeSet;
28 import android.view.Gravity;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.ViewOutlineProvider;
32 import android.view.ViewTreeObserver;
33 import android.widget.LinearLayout;
34 
35 import com.android.systemui.tuner.TunerService;
36 import com.android.systemui.tuner.TunerService.Tunable;
37 import com.android.systemui.util.leak.RotationUtils;
38 
39 /**
40  * Layout for placing two containers at a specific physical position on the device, relative to the
41  * device's hardware, regardless of screen rotation.
42  */
43 public class HardwareUiLayout extends MultiListLayout implements Tunable {
44 
45     private static final String EDGE_BLEED = "sysui_hwui_edge_bleed";
46     private static final String ROUNDED_DIVIDER = "sysui_hwui_rounded_divider";
47     private final int[] mTmp2 = new int[2];
48     private ViewGroup mList;
49     private ViewGroup mSeparatedView;
50     private int mOldHeight;
51     private boolean mAnimating;
52     private AnimatorSet mAnimation;
53     private View mDivision;
54     private HardwareBgDrawable mListBackground;
55     private HardwareBgDrawable mSeparatedViewBackground;
56     private Animator mAnimator;
57     private boolean mCollapse;
58     private int mEndPoint;
59     private boolean mEdgeBleed;
60     private boolean mRoundedDivider;
61     private boolean mRotatedBackground;
62     private boolean mSwapOrientation = true;
63 
HardwareUiLayout(Context context, AttributeSet attrs)64     public HardwareUiLayout(Context context, AttributeSet attrs) {
65         super(context, attrs);
66         // Manually re-initialize mRotation to portrait-mode, since this view must always
67         // be constructed in portrait mode and rotated into the correct initial position.
68         mRotation = ROTATION_NONE;
69         updateSettings();
70     }
71 
72     @Override
getSeparatedView()73     protected ViewGroup getSeparatedView() {
74         return findViewById(com.android.systemui.R.id.separated_button);
75     }
76 
77     @Override
getListView()78     protected ViewGroup getListView() {
79         return findViewById(android.R.id.list);
80     }
81 
82     @Override
onAttachedToWindow()83     protected void onAttachedToWindow() {
84         super.onAttachedToWindow();
85         updateSettings();
86         Dependency.get(TunerService.class).addTunable(this, EDGE_BLEED, ROUNDED_DIVIDER);
87         getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
88     }
89 
90     @Override
onDetachedFromWindow()91     protected void onDetachedFromWindow() {
92         super.onDetachedFromWindow();
93         getViewTreeObserver().removeOnComputeInternalInsetsListener(mInsetsListener);
94         Dependency.get(TunerService.class).removeTunable(this);
95     }
96 
97     @Override
onTuningChanged(String key, String newValue)98     public void onTuningChanged(String key, String newValue) {
99         updateSettings();
100     }
101 
updateSettings()102     private void updateSettings() {
103         mEdgeBleed = Settings.Secure.getInt(getContext().getContentResolver(),
104                 EDGE_BLEED, 0) != 0;
105         mRoundedDivider = Settings.Secure.getInt(getContext().getContentResolver(),
106                 ROUNDED_DIVIDER, 0) != 0;
107         updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding());
108         mListBackground = new HardwareBgDrawable(mRoundedDivider, !mEdgeBleed, getContext());
109         mSeparatedViewBackground = new HardwareBgDrawable(mRoundedDivider, !mEdgeBleed,
110                 getContext());
111         if (mList != null) {
112             mList.setBackground(mListBackground);
113             mSeparatedView.setBackground(mSeparatedViewBackground);
114             requestLayout();
115         }
116     }
117 
updateEdgeMargin(int edge)118     private void updateEdgeMargin(int edge) {
119         if (mList != null) {
120             MarginLayoutParams params = (MarginLayoutParams) mList.getLayoutParams();
121             if (mRotation == ROTATION_LANDSCAPE) {
122                 params.topMargin = edge;
123             } else if (mRotation == ROTATION_SEASCAPE) {
124                 params.bottomMargin = edge;
125             } else {
126                 params.rightMargin = edge;
127             }
128             mList.setLayoutParams(params);
129         }
130 
131         if (mSeparatedView != null) {
132             MarginLayoutParams params = (MarginLayoutParams) mSeparatedView.getLayoutParams();
133             if (mRotation == ROTATION_LANDSCAPE) {
134                 params.topMargin = edge;
135             } else if (mRotation == ROTATION_SEASCAPE) {
136                 params.bottomMargin = edge;
137             } else {
138                 params.rightMargin = edge;
139             }
140             mSeparatedView.setLayoutParams(params);
141         }
142     }
143 
getEdgePadding()144     private int getEdgePadding() {
145         return getContext().getResources().getDimensionPixelSize(R.dimen.edge_margin);
146     }
147 
148     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)149     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
150         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
151         if (mList == null) {
152             if (getChildCount() != 0) {
153                 mList = getListView();
154                 mList.setBackground(mListBackground);
155                 mSeparatedView = getSeparatedView();
156                 mSeparatedView.setBackground(mSeparatedViewBackground);
157                 updateEdgeMargin(mEdgeBleed ? 0 : getEdgePadding());
158                 mOldHeight = mList.getMeasuredHeight();
159 
160                 // Must be called to initialize view rotation correctly.
161                 // Requires LayoutParams, hence why this isn't called during the constructor.
162                 updateRotation();
163             } else {
164                 return;
165             }
166         }
167         int newHeight = mList.getMeasuredHeight();
168         if (newHeight != mOldHeight) {
169             animateChild(mOldHeight, newHeight);
170         }
171 
172         post(() -> updatePaddingAndGravityIfTooTall());
173         post(() -> updatePosition());
174     }
175 
setSwapOrientation(boolean swapOrientation)176     public void setSwapOrientation(boolean swapOrientation) {
177         mSwapOrientation = swapOrientation;
178     }
179 
updateRotation()180     private void updateRotation() {
181         int rotation = RotationUtils.getRotation(getContext());
182         if (rotation != mRotation) {
183             rotate(mRotation, rotation);
184             mRotation = rotation;
185         }
186     }
187 
188     /**
189      *  Requires LayoutParams to be set to work correctly, and therefore must be run after after
190      *  the HardwareUILayout has been added to the view hierarchy.
191      */
rotate(int from, int to)192     protected void rotate(int from, int to) {
193         super.rotate(from, to);
194         if (from != ROTATION_NONE && to != ROTATION_NONE) {
195             // Rather than handling this confusing case, just do 2 rotations.
196             rotate(from, ROTATION_NONE);
197             rotate(ROTATION_NONE, to);
198             return;
199         }
200         if (from == ROTATION_LANDSCAPE || to == ROTATION_SEASCAPE) {
201             rotateRight();
202         } else {
203             rotateLeft();
204         }
205         if (mAdapter.hasSeparatedItems()) {
206             if (from == ROTATION_SEASCAPE || to == ROTATION_SEASCAPE) {
207                 // Separated view has top margin, so seascape separated view need special rotation,
208                 // not a full left or right rotation.
209                 swapLeftAndTop(mSeparatedView);
210             } else if (from == ROTATION_LANDSCAPE) {
211                 rotateRight(mSeparatedView);
212             } else {
213                 rotateLeft(mSeparatedView);
214             }
215         }
216         if (to != ROTATION_NONE) {
217             if (mList instanceof LinearLayout) {
218                 mRotatedBackground = true;
219                 mListBackground.setRotatedBackground(true);
220                 mSeparatedViewBackground.setRotatedBackground(true);
221                 LinearLayout linearLayout = (LinearLayout) mList;
222                 if (mSwapOrientation) {
223                     linearLayout.setOrientation(LinearLayout.HORIZONTAL);
224                     setOrientation(LinearLayout.HORIZONTAL);
225                 }
226                 swapDimens(mList);
227                 swapDimens(mSeparatedView);
228             }
229         } else {
230             if (mList instanceof LinearLayout) {
231                 mRotatedBackground = false;
232                 mListBackground.setRotatedBackground(false);
233                 mSeparatedViewBackground.setRotatedBackground(false);
234                 LinearLayout linearLayout = (LinearLayout) mList;
235                 if (mSwapOrientation) {
236                     linearLayout.setOrientation(LinearLayout.VERTICAL);
237                     setOrientation(LinearLayout.VERTICAL);
238                 }
239                 swapDimens(mList);
240                 swapDimens(mSeparatedView);
241             }
242         }
243     }
244 
245     @Override
onUpdateList()246     public void onUpdateList() {
247         super.onUpdateList();
248 
249         for (int i = 0; i < mAdapter.getCount(); i++) {
250             ViewGroup parent;
251             boolean separated = mAdapter.shouldBeSeparated(i);
252             if (separated) {
253                 parent = getSeparatedView();
254             } else {
255                 parent = getListView();
256             }
257             View v = mAdapter.getView(i, null, parent);
258             parent.addView(v);
259         }
260     }
261 
rotateRight()262     private void rotateRight() {
263         rotateRight(this);
264         rotateRight(mList);
265         swapDimens(this);
266 
267         LayoutParams p = (LayoutParams) mList.getLayoutParams();
268         p.gravity = rotateGravityRight(p.gravity);
269         mList.setLayoutParams(p);
270 
271         LayoutParams separatedViewLayoutParams = (LayoutParams) mSeparatedView.getLayoutParams();
272         separatedViewLayoutParams.gravity = rotateGravityRight(separatedViewLayoutParams.gravity);
273         mSeparatedView.setLayoutParams(separatedViewLayoutParams);
274 
275         setGravity(rotateGravityRight(getGravity()));
276     }
277 
swapDimens(View v)278     private void swapDimens(View v) {
279         ViewGroup.LayoutParams params = v.getLayoutParams();
280         int h = params.width;
281         params.width = params.height;
282         params.height = h;
283         v.setLayoutParams(params);
284     }
285 
rotateGravityRight(int gravity)286     private int rotateGravityRight(int gravity) {
287         int retGravity = 0;
288         int layoutDirection = getLayoutDirection();
289         final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
290         final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
291 
292         switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
293             case Gravity.CENTER_HORIZONTAL:
294                 retGravity |= Gravity.CENTER_VERTICAL;
295                 break;
296             case Gravity.RIGHT:
297                 retGravity |= Gravity.BOTTOM;
298                 break;
299             case Gravity.LEFT:
300             default:
301                 retGravity |= Gravity.TOP;
302                 break;
303         }
304 
305         switch (verticalGravity) {
306             case Gravity.CENTER_VERTICAL:
307                 retGravity |= Gravity.CENTER_HORIZONTAL;
308                 break;
309             case Gravity.BOTTOM:
310                 retGravity |= Gravity.LEFT;
311                 break;
312             case Gravity.TOP:
313             default:
314                 retGravity |= Gravity.RIGHT;
315                 break;
316         }
317         return retGravity;
318     }
319 
rotateLeft()320     private void rotateLeft() {
321         rotateLeft(this);
322         rotateLeft(mList);
323         swapDimens(this);
324 
325         LayoutParams p = (LayoutParams) mList.getLayoutParams();
326         p.gravity = rotateGravityLeft(p.gravity);
327         mList.setLayoutParams(p);
328 
329         LayoutParams separatedViewLayoutParams = (LayoutParams) mSeparatedView.getLayoutParams();
330         separatedViewLayoutParams.gravity = rotateGravityLeft(separatedViewLayoutParams.gravity);
331         mSeparatedView.setLayoutParams(separatedViewLayoutParams);
332 
333         setGravity(rotateGravityLeft(getGravity()));
334     }
335 
rotateGravityLeft(int gravity)336     private int rotateGravityLeft(int gravity) {
337         if (gravity == -1) {
338             gravity = Gravity.TOP | Gravity.START;
339         }
340         int retGravity = 0;
341         int layoutDirection = getLayoutDirection();
342         final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
343         final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
344 
345         switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
346             case Gravity.CENTER_HORIZONTAL:
347                 retGravity |= Gravity.CENTER_VERTICAL;
348                 break;
349             case Gravity.RIGHT:
350                 retGravity |= Gravity.TOP;
351                 break;
352             case Gravity.LEFT:
353             default:
354                 retGravity |= Gravity.BOTTOM;
355                 break;
356         }
357 
358         switch (verticalGravity) {
359             case Gravity.CENTER_VERTICAL:
360                 retGravity |= Gravity.CENTER_HORIZONTAL;
361                 break;
362             case Gravity.BOTTOM:
363                 retGravity |= Gravity.RIGHT;
364                 break;
365             case Gravity.TOP:
366             default:
367                 retGravity |= Gravity.LEFT;
368                 break;
369         }
370         return retGravity;
371     }
372 
rotateLeft(View v)373     private void rotateLeft(View v) {
374         v.setPadding(v.getPaddingTop(), v.getPaddingRight(), v.getPaddingBottom(),
375                 v.getPaddingLeft());
376         MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams();
377         params.setMargins(params.topMargin, params.rightMargin, params.bottomMargin,
378                 params.leftMargin);
379         v.setLayoutParams(params);
380     }
381 
rotateRight(View v)382     private void rotateRight(View v) {
383         v.setPadding(v.getPaddingBottom(), v.getPaddingLeft(), v.getPaddingTop(),
384                 v.getPaddingRight());
385         MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams();
386         params.setMargins(params.bottomMargin, params.leftMargin, params.topMargin,
387                 params.rightMargin);
388         v.setLayoutParams(params);
389     }
390 
swapLeftAndTop(View v)391     private void swapLeftAndTop(View v) {
392         v.setPadding(v.getPaddingTop(), v.getPaddingLeft(), v.getPaddingBottom(),
393                 v.getPaddingRight());
394         MarginLayoutParams params = (MarginLayoutParams) v.getLayoutParams();
395         params.setMargins(params.topMargin, params.leftMargin, params.bottomMargin,
396                 params.rightMargin);
397         v.setLayoutParams(params);
398     }
399 
400     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)401     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
402         super.onLayout(changed, left, top, right, bottom);
403 
404         post(() -> updatePosition());
405 
406     }
407 
animateChild(int oldHeight, int newHeight)408     private void animateChild(int oldHeight, int newHeight) {
409         if (true) return;
410         if (mAnimating) {
411             mAnimation.cancel();
412         }
413         mAnimating = true;
414         mAnimation = new AnimatorSet();
415         mAnimation.addListener(new AnimatorListenerAdapter() {
416             @Override
417             public void onAnimationEnd(Animator animation) {
418                 mAnimating = false;
419             }
420         });
421         int fromTop = mList.getTop();
422         int fromBottom = mList.getBottom();
423         int toTop = fromTop - ((newHeight - oldHeight) / 2);
424         int toBottom = fromBottom + ((newHeight - oldHeight) / 2);
425         ObjectAnimator top = ObjectAnimator.ofInt(mList, "top", fromTop, toTop);
426         top.addUpdateListener(animation -> mListBackground.invalidateSelf());
427         mAnimation.playTogether(top,
428                 ObjectAnimator.ofInt(mList, "bottom", fromBottom, toBottom));
429     }
430 
setDivisionView(View v)431     public void setDivisionView(View v) {
432         mDivision = v;
433         if (mDivision != null) {
434             mDivision.addOnLayoutChangeListener(
435                     (v1, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
436                             updatePosition());
437         }
438         updatePosition();
439     }
440 
updatePosition()441     private void updatePosition() {
442         if (mList == null) return;
443         // If got separated button, setRotatedBackground to false,
444         // all items won't get white background.
445         boolean separated = mAdapter.hasSeparatedItems();
446         mListBackground.setRotatedBackground(separated);
447         mSeparatedViewBackground.setRotatedBackground(separated);
448         if (mDivision != null && mDivision.getVisibility() == VISIBLE) {
449             int index = mRotatedBackground ? 0 : 1;
450             mDivision.getLocationOnScreen(mTmp2);
451             float trans = mRotatedBackground ? mDivision.getTranslationX()
452                     : mDivision.getTranslationY();
453             int viewTop = (int) (mTmp2[index] + trans);
454             mList.getLocationOnScreen(mTmp2);
455             viewTop -= mTmp2[index];
456             setCutPoint(viewTop);
457         } else {
458             setCutPoint(mList.getMeasuredHeight());
459         }
460     }
461 
setCutPoint(int point)462     private void setCutPoint(int point) {
463         int curPoint = mListBackground.getCutPoint();
464         if (curPoint == point) return;
465         if (getAlpha() == 0 || curPoint == 0) {
466             mListBackground.setCutPoint(point);
467             return;
468         }
469         if (mAnimator != null) {
470             if (mEndPoint == point) {
471                 return;
472             }
473             mAnimator.cancel();
474         }
475         mEndPoint = point;
476         mAnimator = ObjectAnimator.ofInt(mListBackground, "cutPoint", curPoint, point);
477         if (mCollapse) {
478             mAnimator.setStartDelay(300);
479             mCollapse = false;
480         }
481         mAnimator.start();
482     }
483 
484     // If current power menu height larger then screen height, remove padding to break power menu
485     // alignment and set menu center vertical within the screen.
updatePaddingAndGravityIfTooTall()486     private void updatePaddingAndGravityIfTooTall() {
487         int defaultTopPadding;
488         int viewsTotalHeight;
489         int separatedViewTopMargin;
490         int screenHeight;
491         int totalHeight;
492         int targetGravity;
493         boolean separated = mAdapter.hasSeparatedItems();
494         MarginLayoutParams params = (MarginLayoutParams) mSeparatedView.getLayoutParams();
495         switch (RotationUtils.getRotation(getContext())) {
496             case RotationUtils.ROTATION_LANDSCAPE:
497                 defaultTopPadding = getPaddingLeft();
498                 viewsTotalHeight = mList.getMeasuredWidth() + mSeparatedView.getMeasuredWidth();
499                 separatedViewTopMargin = separated ? params.leftMargin : 0;
500                 screenHeight = getMeasuredWidth();
501                 targetGravity = Gravity.CENTER_HORIZONTAL|Gravity.TOP;
502                 break;
503             case RotationUtils.ROTATION_SEASCAPE:
504                 defaultTopPadding = getPaddingRight();
505                 viewsTotalHeight = mList.getMeasuredWidth() + mSeparatedView.getMeasuredWidth();
506                 separatedViewTopMargin = separated ? params.leftMargin : 0;
507                 screenHeight = getMeasuredWidth();
508                 targetGravity = Gravity.CENTER_HORIZONTAL|Gravity.BOTTOM;
509                 break;
510             default: // Portrait
511                 defaultTopPadding = getPaddingTop();
512                 viewsTotalHeight = mList.getMeasuredHeight() + mSeparatedView.getMeasuredHeight();
513                 separatedViewTopMargin = separated ? params.topMargin : 0;
514                 screenHeight = getMeasuredHeight();
515                 targetGravity = Gravity.CENTER_VERTICAL|Gravity.RIGHT;
516                 break;
517         }
518         totalHeight = defaultTopPadding + viewsTotalHeight + separatedViewTopMargin;
519         if (totalHeight >= screenHeight) {
520             setPadding(0, 0, 0, 0);
521             setGravity(targetGravity);
522         }
523     }
524 
525     @Override
getOutlineProvider()526     public ViewOutlineProvider getOutlineProvider() {
527         return super.getOutlineProvider();
528     }
529 
setCollapse()530     public void setCollapse() {
531         mCollapse = true;
532     }
533 
534     private final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener = inoutInfo -> {
535         if (mHasOutsideTouch || (mList == null)) {
536             inoutInfo.setTouchableInsets(
537                     ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
538             return;
539         }
540         inoutInfo.setTouchableInsets(
541                 ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT);
542         inoutInfo.contentInsets.set(mList.getLeft(), mList.getTop(),
543                 0, getBottom() - mList.getBottom());
544     };
545 
getAnimationDistance()546     private float getAnimationDistance() {
547         return getContext().getResources().getDimension(
548                 com.android.systemui.R.dimen.global_actions_panel_width) / 2;
549     }
550 
551     @Override
getAnimationOffsetX()552     public float getAnimationOffsetX() {
553         if (RotationUtils.getRotation(mContext) == ROTATION_NONE) {
554             return getAnimationDistance();
555         }
556         return 0;
557     }
558 
559     @Override
getAnimationOffsetY()560     public float getAnimationOffsetY() {
561         switch (RotationUtils.getRotation(getContext())) {
562             case RotationUtils.ROTATION_LANDSCAPE:
563                 return -getAnimationDistance();
564             case RotationUtils.ROTATION_SEASCAPE:
565                 return getAnimationDistance();
566             default: // Portrait
567                 return 0;
568         }
569     }
570 }