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.Canvas; 25 import android.graphics.Rect; 26 import android.util.AttributeSet; 27 import android.view.MotionEvent; 28 import android.view.View; 29 import android.view.WindowInsets; 30 31 import androidx.annotation.Nullable; 32 import androidx.constraintlayout.widget.ConstraintLayout; 33 import androidx.constraintlayout.widget.ConstraintSet; 34 35 import com.android.systemui.fragments.FragmentHostManager.FragmentListener; 36 import com.android.systemui.keyguard.MigrateClocksToBlueprint; 37 import com.android.systemui.plugins.qs.QS; 38 import com.android.systemui.res.R; 39 import com.android.systemui.statusbar.notification.AboveShelfObserver; 40 41 import java.util.ArrayList; 42 import java.util.Comparator; 43 import java.util.function.Consumer; 44 45 /** 46 * The container with notification stack scroller and quick settings inside. 47 */ 48 public class NotificationsQuickSettingsContainer extends ConstraintLayout 49 implements FragmentListener, AboveShelfObserver.HasViewAboveShelfChangedListener { 50 51 private View mQsFrame; 52 private View mStackScroller; 53 private View mKeyguardStatusBar; 54 55 private final ArrayList<View> mDrawingOrderedChildren = new ArrayList<>(); 56 private final ArrayList<View> mLayoutDrawingOrder = new ArrayList<>(); 57 private final Comparator<View> mIndexComparator = Comparator.comparingInt(this::indexOfChild); 58 private Consumer<WindowInsets> mInsetsChangedListener = insets -> {}; 59 private Consumer<QS> mQSFragmentAttachedListener = qs -> {}; 60 private QS mQs; 61 private View mQSContainer; 62 private int mLastQSPaddingBottom; 63 64 /** 65 * These are used to compute the bounding box containing the shade and the notification scrim, 66 * which is then used to drive the Back gesture animation. 67 */ 68 private final Rect mUpperRect = new Rect(); 69 private final Rect mBoundingBoxRect = new Rect(); 70 71 @Nullable 72 private Consumer<Configuration> mConfigurationChangedListener; 73 NotificationsQuickSettingsContainer(Context context, AttributeSet attrs)74 public NotificationsQuickSettingsContainer(Context context, AttributeSet attrs) { 75 super(context, attrs); 76 setOptimizationLevel(getOptimizationLevel() | OPTIMIZATION_GRAPH); 77 } 78 79 @Override onFinishInflate()80 protected void onFinishInflate() { 81 super.onFinishInflate(); 82 mQsFrame = findViewById(R.id.qs_frame); 83 mKeyguardStatusBar = findViewById(R.id.keyguard_header); 84 } 85 setStackScroller(View stackScroller)86 void setStackScroller(View stackScroller) { 87 mStackScroller = stackScroller; 88 } 89 90 @Override onFragmentViewCreated(String tag, Fragment fragment)91 public void onFragmentViewCreated(String tag, Fragment fragment) { 92 mQs = (QS) fragment; 93 mQSFragmentAttachedListener.accept(mQs); 94 mQSContainer = mQs.getView().findViewById(R.id.quick_settings_container); 95 // We need to restore the bottom padding as the fragment may have been recreated due to 96 // some special Configuration change, so we apply the last known padding (this will be 97 // correct even if it has changed while the fragment was destroyed and re-created). 98 setQSContainerPaddingBottom(mLastQSPaddingBottom); 99 } 100 101 @Override onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf)102 public void onHasViewsAboveShelfChanged(boolean hasViewsAboveShelf) { 103 invalidate(); 104 } 105 106 @Override onConfigurationChanged(Configuration newConfig)107 protected void onConfigurationChanged(Configuration newConfig) { 108 super.onConfigurationChanged(newConfig); 109 if (mConfigurationChangedListener != null) { 110 mConfigurationChangedListener.accept(newConfig); 111 } 112 } 113 setConfigurationChangedListener(Consumer<Configuration> listener)114 public void setConfigurationChangedListener(Consumer<Configuration> listener) { 115 mConfigurationChangedListener = listener; 116 } 117 setNotificationsMarginBottom(int margin)118 public void setNotificationsMarginBottom(int margin) { 119 MarginLayoutParams params = (MarginLayoutParams) mStackScroller.getLayoutParams(); 120 params.bottomMargin = margin; 121 mStackScroller.setLayoutParams(params); 122 } 123 setQSContainerPaddingBottom(int paddingBottom)124 public void setQSContainerPaddingBottom(int paddingBottom) { 125 mLastQSPaddingBottom = paddingBottom; 126 if (mQSContainer != null) { 127 mQSContainer.setPadding( 128 mQSContainer.getPaddingLeft(), 129 mQSContainer.getPaddingTop(), 130 mQSContainer.getPaddingRight(), 131 paddingBottom 132 ); 133 } 134 } 135 setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener)136 public void setInsetsChangedListener(Consumer<WindowInsets> onInsetsChangedListener) { 137 mInsetsChangedListener = onInsetsChangedListener; 138 } 139 removeOnInsetsChangedListener()140 public void removeOnInsetsChangedListener() { 141 mInsetsChangedListener = insets -> {}; 142 } 143 setQSFragmentAttachedListener(Consumer<QS> qsFragmentAttachedListener)144 public void setQSFragmentAttachedListener(Consumer<QS> qsFragmentAttachedListener) { 145 mQSFragmentAttachedListener = qsFragmentAttachedListener; 146 // listener might be attached after fragment is attached 147 if (mQs != null) { 148 mQSFragmentAttachedListener.accept(mQs); 149 } 150 } 151 removeQSFragmentAttachedListener()152 public void removeQSFragmentAttachedListener() { 153 mQSFragmentAttachedListener = qs -> {}; 154 } 155 156 @Override onApplyWindowInsets(WindowInsets insets)157 public WindowInsets onApplyWindowInsets(WindowInsets insets) { 158 mInsetsChangedListener.accept(insets); 159 return insets; 160 } 161 162 @Override dispatchDraw(Canvas canvas)163 protected void dispatchDraw(Canvas canvas) { 164 mDrawingOrderedChildren.clear(); 165 mLayoutDrawingOrder.clear(); 166 if (mKeyguardStatusBar.getVisibility() == View.VISIBLE) { 167 mDrawingOrderedChildren.add(mKeyguardStatusBar); 168 mLayoutDrawingOrder.add(mKeyguardStatusBar); 169 } 170 if (mQsFrame.getVisibility() == View.VISIBLE) { 171 mDrawingOrderedChildren.add(mQsFrame); 172 mLayoutDrawingOrder.add(mQsFrame); 173 } 174 if (mStackScroller.getVisibility() == View.VISIBLE) { 175 mDrawingOrderedChildren.add(mStackScroller); 176 mLayoutDrawingOrder.add(mStackScroller); 177 } 178 179 // Let's now find the order that the view has when drawing regularly by sorting 180 mLayoutDrawingOrder.sort(mIndexComparator); 181 super.dispatchDraw(canvas); 182 } 183 184 @Override dispatchTouchEvent(MotionEvent ev)185 public boolean dispatchTouchEvent(MotionEvent ev) { 186 return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev, 187 super.dispatchTouchEvent(ev)); 188 } 189 190 @Override drawChild(Canvas canvas, View child, long drawingTime)191 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 192 if (MigrateClocksToBlueprint.isEnabled()) { 193 return super.drawChild(canvas, child, drawingTime); 194 } 195 int layoutIndex = mLayoutDrawingOrder.indexOf(child); 196 if (layoutIndex >= 0) { 197 return super.drawChild(canvas, mDrawingOrderedChildren.get(layoutIndex), drawingTime); 198 } else { 199 return super.drawChild(canvas, child, drawingTime); 200 } 201 } 202 applyConstraints(ConstraintSet constraintSet)203 public void applyConstraints(ConstraintSet constraintSet) { 204 constraintSet.applyTo(this); 205 } 206 207 /** 208 * Scale multiple elements in tandem, for the predictive back animation. 209 * This is how the Shade responds to the Back gesture (by scaling). 210 * Without the common center, individual elements will scale about their respective centers. 211 * Scaling the entire NotificationsQuickSettingsContainer will also resize the shade header 212 * (which we don't want). 213 */ applyBackScaling(float scale, boolean usingSplitShade)214 public void applyBackScaling(float scale, boolean usingSplitShade) { 215 if (mStackScroller == null || mQSContainer == null) { 216 return; 217 } 218 219 mQSContainer.getBoundsOnScreen(mUpperRect); 220 mStackScroller.getBoundsOnScreen(mBoundingBoxRect); 221 mBoundingBoxRect.union(mUpperRect); 222 223 float cx = mBoundingBoxRect.centerX(); 224 float cy = mBoundingBoxRect.centerY(); 225 226 mQSContainer.setPivotX(cx); 227 mQSContainer.setPivotY(cy); 228 mQSContainer.setScaleX(scale); 229 mQSContainer.setScaleY(scale); 230 231 // When in large-screen split-shade mode, the notification stack scroller scales correctly 232 // only if the pivot point is at the left edge of the screen (because of its dimensions). 233 // When not in large-screen split-shade mode, we can scale correctly via the (cx,cy) above. 234 mStackScroller.setPivotX(usingSplitShade ? 0.0f : cx); 235 mStackScroller.setPivotY(cy); 236 mStackScroller.setScaleX(scale); 237 mStackScroller.setScaleY(scale); 238 } 239 } 240