• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.systemui.shade;
18 
19 import static androidx.constraintlayout.core.widgets.Optimizer.OPTIMIZATION_GRAPH;
20 
21 import android.app.Fragment;
22 import android.content.Context;
23 import android.content.res.Configuration;
24 import android.graphics.Rect;
25 import android.util.AttributeSet;
26 import android.view.MotionEvent;
27 import android.view.View;
28 import android.view.WindowInsets;
29 
30 import androidx.annotation.Nullable;
31 import androidx.constraintlayout.widget.ConstraintLayout;
32 import androidx.constraintlayout.widget.ConstraintSet;
33 
34 import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
35 import com.android.systemui.plugins.qs.QS;
36 import com.android.systemui.res.R;
37 import com.android.systemui.statusbar.notification.AboveShelfObserver;
38 
39 import java.util.function.Consumer;
40 
41 /**
42  * The container with notification stack scroller and quick settings inside.
43  */
44 public class NotificationsQuickSettingsContainer extends ConstraintLayout
45         implements FragmentListener, AboveShelfObserver.HasViewAboveShelfChangedListener {
46 
47     private View mQsFrame;
48     private View mStackScroller;
49 
50     private Consumer<WindowInsets> mInsetsChangedListener = insets -> {};
51     private Consumer<QS> mQSFragmentAttachedListener = qs -> {};
52     private QS mQs;
53     private View mQSContainer;
54     private int mLastQSPaddingBottom;
55 
56     /**
57      *  These are used to compute the bounding box containing the shade and the notification scrim,
58      *  which is then used to drive the Back gesture animation.
59      */
60     private final Rect mUpperRect = new Rect();
61     private final Rect mBoundingBoxRect = new Rect();
62 
63     @Nullable
64     private Consumer<Configuration> mConfigurationChangedListener;
65 
NotificationsQuickSettingsContainer(Context context, AttributeSet attrs)66     public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) {
67         super(context, attrs);
68         setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH);
69     }
70 
71     @Override
onFinishInflate()72     protected void onFinishInflate() {
73         super.onFinishInflate();
74         mQsFrame = findViewById(R.id.qs_frame);
75     }
76 
setStackScroller(View stackScroller)77     void setStackScroller(View stackScroller) {
78         mStackScroller = stackScroller;
79     }
80 
81     @Override
onFragmentViewCreated(String tag, Fragment fragment)82     public void onFragmentViewCreated(String tag, Fragment fragment) {
83         mQs = (QS) fragment;
84         mQSFragmentAttachedListener.accept(mQs);
85         mQSContainer = mQs.getView().findViewById(R.id.quick_settings_container);
86         // We need to restore the bottom padding as the fragment may have been recreated due to
87         // some special Configuration change, so we apply the last known padding (this will be
88         // correct even if it has changed while the fragment was destroyed and re-created).
89         setQSContainerPaddingBottom(mLastQSPaddingBottom);
90     }
91 
92     @Override
onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf)93     public void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf) {
94         invalidate();
95     }
96 
97     @Override
onConfigurationChanged(Configuration newConfig)98     protected void onConfigurationChanged(Configuration newConfig) {
99         super.onConfigurationChanged(newConfig);
100         if (mConfigurationChangedListener != null) {
101             mConfigurationChangedListener.accept(newConfig);
102         }
103     }
104 
setConfigurationChangedListener(Consumer<Configuration> listener)105     public void setConfigurationChangedListener(Consumer<Configuration> listener) {
106         mConfigurationChangedListener = listener;
107     }
108 
setNotificationsMarginBottom(int margin)109     public void setNotificationsMarginBottom(int margin) {
110         MarginLayoutParams params = (MarginLayoutParams) mStackScroller.getLayoutParams();
111         params.bottomMargin = margin;
112         mStackScroller.setLayoutParams(params);
113     }
114 
setQSContainerPaddingBottom(int paddingBottom)115     public void setQSContainerPaddingBottom(int paddingBottom) {
116         mLastQSPaddingBottom = paddingBottom;
117         if (mQSContainer != null) {
118             mQSContainer.setPadding(
119                     mQSContainer.getPaddingLeft(),
120                     mQSContainer.getPaddingTop(),
121                     mQSContainer.getPaddingRight(),
122                     paddingBottom
123             );
124         }
125     }
126 
setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener)127     public void setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener) {
128         mInsetsChangedListener = onInsetsChangedListener;
129     }
130 
removeOnInsetsChangedListener()131     public void removeOnInsetsChangedListener() {
132         mInsetsChangedListener = insets -> {};
133     }
134 
setQSFragmentAttachedListener(Consumer<QS> qsFragmentAttachedListener)135     public void setQSFragmentAttachedListener(Consumer<QS> qsFragmentAttachedListener) {
136         mQSFragmentAttachedListener = qsFragmentAttachedListener;
137         // listener might be attached after fragment is attached
138         if (mQs != null) {
139             mQSFragmentAttachedListener.accept(mQs);
140         }
141     }
142 
removeQSFragmentAttachedListener()143     public void removeQSFragmentAttachedListener() {
144         mQSFragmentAttachedListener = qs -> {};
145     }
146 
147     @Override
onApplyWindowInsets(WindowInsets insets)148     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
149         mInsetsChangedListener.accept(insets);
150         return insets;
151     }
152 
153     @Override
dispatchTouchEvent(MotionEvent ev)154     public boolean dispatchTouchEvent(MotionEvent ev) {
155         return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
156                 super.dispatchTouchEvent(ev));
157     }
158 
applyConstraints(ConstraintSet constraintSet)159     public void applyConstraints(ConstraintSet constraintSet) {
160         constraintSet.applyTo(this);
161     }
162 
163     /**
164      *  Scale multiple elements in tandem, for the predictive back animation.
165      *  This is how the Shade responds to the Back gesture (by scaling).
166      *  Without the common center, individual elements will scale about their respective centers.
167      *  Scaling the entire NotificationsQuickSettingsContainer will also resize the shade header
168      *  (which we don't want).
169      */
applyBackScaling(float scale, boolean usingSplitShade)170     public void applyBackScaling(float scale, boolean usingSplitShade) {
171         if (mStackScroller == null || mQSContainer == null) {
172             return;
173         }
174 
175         mQSContainer.getBoundsOnScreen(mUpperRect);
176         mStackScroller.getBoundsOnScreen(mBoundingBoxRect);
177         mBoundingBoxRect.union(mUpperRect);
178 
179         float cx = mBoundingBoxRect.centerX();
180         float cy = mBoundingBoxRect.centerY();
181 
182         mQSContainer.setPivotX(cx);
183         mQSContainer.setPivotY(cy);
184         mQSContainer.setScaleX(scale);
185         mQSContainer.setScaleY(scale);
186 
187         // When in large-screen split-shade mode, the notification stack scroller scales correctly
188         // only if the pivot point is at the left edge of the screen (because of its dimensions).
189         // When not in large-screen split-shade mode, we can scale correctly via the (cx,cy) above.
190         mStackScroller.setPivotX(usingSplitShade ? 0.0f : cx);
191         mStackScroller.setPivotY(cy);
192         mStackScroller.setScaleX(scale);
193         mStackScroller.setScaleY(scale);
194     }
195 }
196