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