blob: 4e7f4f38ebd3a4736c101b3f746b2e3d2e5cb5ac [file] [log] [blame]
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001/*
2 * Copyright (C) 2018 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
17package com.android.systemui.statusbar.phone;
18
felkachang3d00f352018-05-22 12:53:50 +080019import android.graphics.Point;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080020import android.graphics.Rect;
felkachang7749c9a2018-06-11 15:56:15 +080021import android.view.DisplayCutout;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080022import android.view.View;
felkachang3d00f352018-05-22 12:53:50 +080023import android.view.WindowInsets;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080024
25import com.android.internal.annotations.VisibleForTesting;
26import com.android.systemui.Dependency;
27import com.android.systemui.R;
Selim Cinekd03518c2018-03-15 12:13:51 -070028import com.android.systemui.statusbar.CrossFadeHelper;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080029import com.android.systemui.statusbar.ExpandableNotificationRow;
30import com.android.systemui.statusbar.HeadsUpStatusBarView;
31import com.android.systemui.statusbar.NotificationData;
32import com.android.systemui.statusbar.policy.DarkIconDispatcher;
33import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
34import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
35
Selim Cinek60ffea62018-03-22 13:16:44 -070036import java.util.function.BiConsumer;
37import java.util.function.Consumer;
38
Selim Cinekaa9db1f2018-02-27 17:35:47 -080039/**
40 * Controls the appearance of heads up notifications in the icon area and the header itself.
41 */
Selim Cinekf0c79e12018-05-14 17:17:31 -070042public class HeadsUpAppearanceController implements OnHeadsUpChangedListener,
Selim Cinekaa9db1f2018-02-27 17:35:47 -080043 DarkIconDispatcher.DarkReceiver {
Selim Cinekd03518c2018-03-15 12:13:51 -070044 public static final int CONTENT_FADE_DURATION = 110;
45 public static final int CONTENT_FADE_DELAY = 100;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080046 private final NotificationIconAreaController mNotificationIconAreaController;
47 private final HeadsUpManagerPhone mHeadsUpManager;
48 private final NotificationStackScrollLayout mStackScroller;
49 private final HeadsUpStatusBarView mHeadsUpStatusBarView;
50 private final View mClockView;
51 private final DarkIconDispatcher mDarkIconDispatcher;
Selim Cinek60ffea62018-03-22 13:16:44 -070052 private final NotificationPanelView mPanelView;
53 private final Consumer<ExpandableNotificationRow>
54 mSetTrackingHeadsUp = this::setTrackingHeadsUp;
55 private final Runnable mUpdatePanelTranslation = this::updatePanelTranslation;
56 private final BiConsumer<Float, Float> mSetExpandedHeight = this::setExpandedHeight;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080057 private float mExpandedHeight;
58 private boolean mIsExpanded;
59 private float mExpandFraction;
60 private ExpandableNotificationRow mTrackedChild;
61 private boolean mShown;
Selim Cinek60ffea62018-03-22 13:16:44 -070062 private final View.OnLayoutChangeListener mStackScrollLayoutChangeListener =
63 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
64 -> updatePanelTranslation();
felkachang3d00f352018-05-22 12:53:50 +080065 Point mPoint;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080066
67 public HeadsUpAppearanceController(
68 NotificationIconAreaController notificationIconAreaController,
69 HeadsUpManagerPhone headsUpManager,
70 View statusbarView) {
71 this(notificationIconAreaController, headsUpManager,
72 statusbarView.findViewById(R.id.heads_up_status_bar_view),
73 statusbarView.findViewById(R.id.notification_stack_scroller),
74 statusbarView.findViewById(R.id.notification_panel),
75 statusbarView.findViewById(R.id.clock));
76 }
77
78 @VisibleForTesting
79 public HeadsUpAppearanceController(
80 NotificationIconAreaController notificationIconAreaController,
81 HeadsUpManagerPhone headsUpManager,
82 HeadsUpStatusBarView headsUpStatusBarView,
83 NotificationStackScrollLayout stackScroller,
84 NotificationPanelView panelView,
85 View clockView) {
86 mNotificationIconAreaController = notificationIconAreaController;
87 mHeadsUpManager = headsUpManager;
88 mHeadsUpManager.addListener(this);
89 mHeadsUpStatusBarView = headsUpStatusBarView;
Selim Cinek332c23f2018-03-16 17:37:50 -070090 headsUpStatusBarView.setOnDrawingRectChangedListener(
91 () -> updateIsolatedIconLocation(true /* requireUpdate */));
Selim Cinekaa9db1f2018-02-27 17:35:47 -080092 mStackScroller = stackScroller;
Selim Cinek60ffea62018-03-22 13:16:44 -070093 mPanelView = panelView;
94 panelView.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
95 panelView.addVerticalTranslationListener(mUpdatePanelTranslation);
Selim Cinek332c23f2018-03-16 17:37:50 -070096 panelView.setHeadsUpAppearanceController(this);
Selim Cinek60ffea62018-03-22 13:16:44 -070097 mStackScroller.addOnExpandedHeightListener(mSetExpandedHeight);
98 mStackScroller.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
Selim Cinekf0c79e12018-05-14 17:17:31 -070099 mStackScroller.setHeadsUpAppearanceController(this);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800100 mClockView = clockView;
101 mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
102 mDarkIconDispatcher.addDarkReceiver(this);
103 }
104
Selim Cinek60ffea62018-03-22 13:16:44 -0700105
106 public void destroy() {
107 mHeadsUpManager.removeListener(this);
108 mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
109 mPanelView.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
110 mPanelView.removeVerticalTranslationListener(mUpdatePanelTranslation);
111 mPanelView.setHeadsUpAppearanceController(null);
112 mStackScroller.removeOnExpandedHeightListener(mSetExpandedHeight);
113 mStackScroller.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
114 mDarkIconDispatcher.removeDarkReceiver(this);
115 }
116
Selim Cinek332c23f2018-03-16 17:37:50 -0700117 private void updateIsolatedIconLocation(boolean requireStateUpdate) {
118 mNotificationIconAreaController.setIsolatedIconLocation(
119 mHeadsUpStatusBarView.getIconDrawingRect(), requireStateUpdate);
120 }
121
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800122 @Override
123 public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
124 updateTopEntry();
125 updateHeader(headsUp.getEntry());
126 }
127
felkachang3d00f352018-05-22 12:53:50 +0800128 /** To count the distance from the window right boundary to scroller right boundary. The
129 * distance formula is the following:
130 * Y = screenSize - (SystemWindow's width + Scroller.getRight())
131 * There are four modes MUST to be considered in Cut Out of RTL.
132 * No Cut Out:
133 * Scroller + NB
134 * NB + Scroller
135 * => SystemWindow = NavigationBar's width
136 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
137 * Corner Cut Out or Tall Cut Out:
138 * cut out + Scroller + NB
139 * NB + Scroller + cut out
140 * => SystemWindow = NavigationBar's width
141 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
142 * Double Cut Out:
143 * cut out left + Scroller + (NB + cut out right)
144 * SystemWindow = NavigationBar's width + cut out right width
145 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
146 * (cut out left + NB) + Scroller + cut out right
147 * SystemWindow = NavigationBar's width + cut out left width
148 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
149 * @return the translation X value for RTL. In theory, it should be negative. i.e. -Y
150 */
151 private int getRtlTranslation() {
felkachang3d00f352018-05-22 12:53:50 +0800152 if (mPoint == null) {
153 mPoint = new Point();
154 }
155
156 int realDisplaySize = 0;
157 if (mStackScroller.getDisplay() != null) {
158 mStackScroller.getDisplay().getRealSize(mPoint);
159 realDisplaySize = mPoint.x;
160 }
161
162 WindowInsets windowInset = mStackScroller.getRootWindowInsets();
felkachang7749c9a2018-06-11 15:56:15 +0800163 DisplayCutout cutout = (windowInset != null) ? windowInset.getDisplayCutout() : null;
164 int sysWinLeft = (windowInset != null) ? windowInset.getStableInsetLeft() : 0;
165 int sysWinRight = (windowInset != null) ? windowInset.getStableInsetRight() : 0;
166 int cutoutLeft = (cutout != null) ? cutout.getSafeInsetLeft() : 0;
167 int cutoutRight = (cutout != null) ? cutout.getSafeInsetRight() : 0;
168 int leftInset = Math.max(sysWinLeft, cutoutLeft);
169 int rightInset = Math.max(sysWinRight, cutoutRight);
170
171 return leftInset + mStackScroller.getRight() + rightInset - realDisplaySize;
felkachang3d00f352018-05-22 12:53:50 +0800172 }
173
Selim Cinek332c23f2018-03-16 17:37:50 -0700174 public void updatePanelTranslation() {
felkachang3d00f352018-05-22 12:53:50 +0800175 float newTranslation;
176 if (mStackScroller.isLayoutRtl()) {
177 newTranslation = getRtlTranslation();
178 } else {
179 newTranslation = mStackScroller.getLeft();
180 }
181 newTranslation += mStackScroller.getTranslationX();
felkachange8a35362018-05-18 20:11:38 +0800182 mHeadsUpStatusBarView.setPanelTranslation(newTranslation);
Selim Cinek332c23f2018-03-16 17:37:50 -0700183 }
184
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800185 private void updateTopEntry() {
186 NotificationData.Entry newEntry = null;
187 if (!mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp()) {
188 newEntry = mHeadsUpManager.getTopEntry();
189 }
190 NotificationData.Entry previousEntry = mHeadsUpStatusBarView.getShowingEntry();
191 mHeadsUpStatusBarView.setEntry(newEntry);
192 if (newEntry != previousEntry) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700193 boolean animateIsolation = false;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800194 if (newEntry == null) {
195 // no heads up anymore, lets start the disappear animation
196
197 setShown(false);
Selim Cinekd03518c2018-03-15 12:13:51 -0700198 animateIsolation = !mIsExpanded;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800199 } else if (previousEntry == null) {
200 // We now have a headsUp and didn't have one before. Let's start the disappear
201 // animation
202 setShown(true);
Selim Cinek332c23f2018-03-16 17:37:50 -0700203 animateIsolation = !mIsExpanded;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800204 }
Selim Cinek332c23f2018-03-16 17:37:50 -0700205 updateIsolatedIconLocation(false /* requireUpdate */);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800206 mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
Selim Cinek332c23f2018-03-16 17:37:50 -0700207 : newEntry.icon, animateIsolation);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800208 }
209 }
210
211 private void setShown(boolean isShown) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700212 if (mShown != isShown) {
213 mShown = isShown;
214 if (isShown) {
215 mHeadsUpStatusBarView.setVisibility(View.VISIBLE);
216 CrossFadeHelper.fadeIn(mHeadsUpStatusBarView, CONTENT_FADE_DURATION /* duration */,
217 CONTENT_FADE_DELAY /* delay */);
218 CrossFadeHelper.fadeOut(mClockView, CONTENT_FADE_DURATION/* duration */,
219 0 /* delay */, () -> mClockView.setVisibility(View.INVISIBLE));
220 } else {
221 CrossFadeHelper.fadeIn(mClockView, CONTENT_FADE_DURATION /* duration */,
222 CONTENT_FADE_DELAY /* delay */);
223 CrossFadeHelper.fadeOut(mHeadsUpStatusBarView, CONTENT_FADE_DURATION/* duration */,
224 0 /* delay */, () -> mHeadsUpStatusBarView.setVisibility(View.GONE));
225
226 }
227 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800228 }
229
230 @VisibleForTesting
231 public boolean isShown() {
232 return mShown;
233 }
234
Selim Cinek332c23f2018-03-16 17:37:50 -0700235 /**
236 * Should the headsup status bar view be visible right now? This may be different from isShown,
237 * since the headsUp manager might not have notified us yet of the state change.
238 *
239 * @return if the heads up status bar view should be shown
240 */
241 public boolean shouldBeVisible() {
242 return !mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp();
243 }
244
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800245 @Override
246 public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
247 updateTopEntry();
248 updateHeader(headsUp.getEntry());
249 }
250
251 public void setExpandedHeight(float expandedHeight, float appearFraction) {
252 boolean changedHeight = expandedHeight != mExpandedHeight;
253 mExpandedHeight = expandedHeight;
254 mExpandFraction = appearFraction;
255 boolean isExpanded = expandedHeight > 0;
256 if (changedHeight) {
257 updateHeadsUpHeaders();
258 }
259 if (isExpanded != mIsExpanded) {
260 mIsExpanded = isExpanded;
261 updateTopEntry();
262 }
263 }
264
265 /**
266 * Set a headsUp to be tracked, meaning that it is currently being pulled down after being
267 * in a pinned state on the top. The expand animation is different in that case and we need
268 * to update the header constantly afterwards.
269 *
270 * @param trackedChild the tracked headsUp or null if it's not tracking anymore.
271 */
272 public void setTrackingHeadsUp(ExpandableNotificationRow trackedChild) {
273 ExpandableNotificationRow previousTracked = mTrackedChild;
274 mTrackedChild = trackedChild;
275 if (previousTracked != null) {
276 updateHeader(previousTracked.getEntry());
277 }
278 }
279
280 private void updateHeadsUpHeaders() {
281 mHeadsUpManager.getAllEntries().forEach(entry -> {
282 updateHeader(entry);
283 });
284 }
285
Selim Cinekf0c79e12018-05-14 17:17:31 -0700286 public void updateHeader(NotificationData.Entry entry) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800287 ExpandableNotificationRow row = entry.row;
288 float headerVisibleAmount = 1.0f;
Selim Cinekf0c79e12018-05-14 17:17:31 -0700289 if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800290 headerVisibleAmount = mExpandFraction;
291 }
292 row.setHeaderVisibleAmount(headerVisibleAmount);
293 }
294
295 @Override
296 public void onDarkChanged(Rect area, float darkIntensity, int tint) {
297 mHeadsUpStatusBarView.onDarkChanged(area, darkIntensity, tint);
298 }
299
300 public void setPublicMode(boolean publicMode) {
301 mHeadsUpStatusBarView.setPublicMode(publicMode);
302 updateTopEntry();
303 }
304}