blob: c3be09a87e336d718c57b61efabf91c91a770fbd [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;
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +090051 private final View mOperatorNameView;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080052 private final DarkIconDispatcher mDarkIconDispatcher;
Selim Cinek60ffea62018-03-22 13:16:44 -070053 private final NotificationPanelView mPanelView;
54 private final Consumer<ExpandableNotificationRow>
55 mSetTrackingHeadsUp = this::setTrackingHeadsUp;
56 private final Runnable mUpdatePanelTranslation = this::updatePanelTranslation;
57 private final BiConsumer<Float, Float> mSetExpandedHeight = this::setExpandedHeight;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080058 private float mExpandedHeight;
59 private boolean mIsExpanded;
60 private float mExpandFraction;
61 private ExpandableNotificationRow mTrackedChild;
62 private boolean mShown;
Selim Cinek60ffea62018-03-22 13:16:44 -070063 private final View.OnLayoutChangeListener mStackScrollLayoutChangeListener =
64 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
65 -> updatePanelTranslation();
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +090066 private boolean mAnimationsEnabled = true;
felkachang3d00f352018-05-22 12:53:50 +080067 Point mPoint;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080068
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +090069
Selim Cinekaa9db1f2018-02-27 17:35:47 -080070 public HeadsUpAppearanceController(
71 NotificationIconAreaController notificationIconAreaController,
72 HeadsUpManagerPhone headsUpManager,
73 View statusbarView) {
74 this(notificationIconAreaController, headsUpManager,
75 statusbarView.findViewById(R.id.heads_up_status_bar_view),
76 statusbarView.findViewById(R.id.notification_stack_scroller),
77 statusbarView.findViewById(R.id.notification_panel),
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +090078 statusbarView.findViewById(R.id.clock),
79 statusbarView.findViewById(R.id.operator_name_frame));
Selim Cinekaa9db1f2018-02-27 17:35:47 -080080 }
81
82 @VisibleForTesting
83 public HeadsUpAppearanceController(
84 NotificationIconAreaController notificationIconAreaController,
85 HeadsUpManagerPhone headsUpManager,
86 HeadsUpStatusBarView headsUpStatusBarView,
87 NotificationStackScrollLayout stackScroller,
88 NotificationPanelView panelView,
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +090089 View clockView,
90 View operatorNameView) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -080091 mNotificationIconAreaController = notificationIconAreaController;
92 mHeadsUpManager = headsUpManager;
93 mHeadsUpManager.addListener(this);
94 mHeadsUpStatusBarView = headsUpStatusBarView;
Selim Cinek332c23f2018-03-16 17:37:50 -070095 headsUpStatusBarView.setOnDrawingRectChangedListener(
96 () -> updateIsolatedIconLocation(true /* requireUpdate */));
Selim Cinekaa9db1f2018-02-27 17:35:47 -080097 mStackScroller = stackScroller;
Selim Cinek60ffea62018-03-22 13:16:44 -070098 mPanelView = panelView;
99 panelView.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
100 panelView.addVerticalTranslationListener(mUpdatePanelTranslation);
Selim Cinek332c23f2018-03-16 17:37:50 -0700101 panelView.setHeadsUpAppearanceController(this);
Selim Cinek60ffea62018-03-22 13:16:44 -0700102 mStackScroller.addOnExpandedHeightListener(mSetExpandedHeight);
103 mStackScroller.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
Selim Cinekf0c79e12018-05-14 17:17:31 -0700104 mStackScroller.setHeadsUpAppearanceController(this);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800105 mClockView = clockView;
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +0900106 mOperatorNameView = operatorNameView;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800107 mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
108 mDarkIconDispatcher.addDarkReceiver(this);
109 }
110
Selim Cinek60ffea62018-03-22 13:16:44 -0700111
112 public void destroy() {
113 mHeadsUpManager.removeListener(this);
114 mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
115 mPanelView.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
116 mPanelView.removeVerticalTranslationListener(mUpdatePanelTranslation);
117 mPanelView.setHeadsUpAppearanceController(null);
118 mStackScroller.removeOnExpandedHeightListener(mSetExpandedHeight);
119 mStackScroller.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
120 mDarkIconDispatcher.removeDarkReceiver(this);
121 }
122
Selim Cinek332c23f2018-03-16 17:37:50 -0700123 private void updateIsolatedIconLocation(boolean requireStateUpdate) {
124 mNotificationIconAreaController.setIsolatedIconLocation(
125 mHeadsUpStatusBarView.getIconDrawingRect(), requireStateUpdate);
126 }
127
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800128 @Override
129 public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
130 updateTopEntry();
131 updateHeader(headsUp.getEntry());
132 }
133
felkachang3d00f352018-05-22 12:53:50 +0800134 /** To count the distance from the window right boundary to scroller right boundary. The
135 * distance formula is the following:
136 * Y = screenSize - (SystemWindow's width + Scroller.getRight())
137 * There are four modes MUST to be considered in Cut Out of RTL.
138 * No Cut Out:
139 * Scroller + NB
140 * NB + Scroller
141 * => SystemWindow = NavigationBar's width
142 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
143 * Corner Cut Out or Tall Cut Out:
144 * cut out + Scroller + NB
145 * NB + Scroller + cut out
146 * => SystemWindow = NavigationBar's width
147 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
148 * Double Cut Out:
149 * cut out left + Scroller + (NB + cut out right)
150 * SystemWindow = NavigationBar's width + cut out right width
151 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
152 * (cut out left + NB) + Scroller + cut out right
153 * SystemWindow = NavigationBar's width + cut out left width
154 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
155 * @return the translation X value for RTL. In theory, it should be negative. i.e. -Y
156 */
157 private int getRtlTranslation() {
felkachang3d00f352018-05-22 12:53:50 +0800158 if (mPoint == null) {
159 mPoint = new Point();
160 }
161
162 int realDisplaySize = 0;
163 if (mStackScroller.getDisplay() != null) {
164 mStackScroller.getDisplay().getRealSize(mPoint);
165 realDisplaySize = mPoint.x;
166 }
167
168 WindowInsets windowInset = mStackScroller.getRootWindowInsets();
felkachang7749c9a2018-06-11 15:56:15 +0800169 DisplayCutout cutout = (windowInset != null) ? windowInset.getDisplayCutout() : null;
170 int sysWinLeft = (windowInset != null) ? windowInset.getStableInsetLeft() : 0;
171 int sysWinRight = (windowInset != null) ? windowInset.getStableInsetRight() : 0;
172 int cutoutLeft = (cutout != null) ? cutout.getSafeInsetLeft() : 0;
173 int cutoutRight = (cutout != null) ? cutout.getSafeInsetRight() : 0;
174 int leftInset = Math.max(sysWinLeft, cutoutLeft);
175 int rightInset = Math.max(sysWinRight, cutoutRight);
176
177 return leftInset + mStackScroller.getRight() + rightInset - realDisplaySize;
felkachang3d00f352018-05-22 12:53:50 +0800178 }
179
Selim Cinek332c23f2018-03-16 17:37:50 -0700180 public void updatePanelTranslation() {
felkachang3d00f352018-05-22 12:53:50 +0800181 float newTranslation;
182 if (mStackScroller.isLayoutRtl()) {
183 newTranslation = getRtlTranslation();
184 } else {
185 newTranslation = mStackScroller.getLeft();
186 }
187 newTranslation += mStackScroller.getTranslationX();
felkachange8a35362018-05-18 20:11:38 +0800188 mHeadsUpStatusBarView.setPanelTranslation(newTranslation);
Selim Cinek332c23f2018-03-16 17:37:50 -0700189 }
190
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800191 private void updateTopEntry() {
192 NotificationData.Entry newEntry = null;
193 if (!mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp()) {
194 newEntry = mHeadsUpManager.getTopEntry();
195 }
196 NotificationData.Entry previousEntry = mHeadsUpStatusBarView.getShowingEntry();
197 mHeadsUpStatusBarView.setEntry(newEntry);
198 if (newEntry != previousEntry) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700199 boolean animateIsolation = false;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800200 if (newEntry == null) {
201 // no heads up anymore, lets start the disappear animation
202
203 setShown(false);
Selim Cinekd03518c2018-03-15 12:13:51 -0700204 animateIsolation = !mIsExpanded;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800205 } else if (previousEntry == null) {
206 // We now have a headsUp and didn't have one before. Let's start the disappear
207 // animation
208 setShown(true);
Selim Cinek332c23f2018-03-16 17:37:50 -0700209 animateIsolation = !mIsExpanded;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800210 }
Selim Cinek332c23f2018-03-16 17:37:50 -0700211 updateIsolatedIconLocation(false /* requireUpdate */);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800212 mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
Selim Cinek332c23f2018-03-16 17:37:50 -0700213 : newEntry.icon, animateIsolation);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800214 }
215 }
216
217 private void setShown(boolean isShown) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700218 if (mShown != isShown) {
219 mShown = isShown;
220 if (isShown) {
221 mHeadsUpStatusBarView.setVisibility(View.VISIBLE);
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +0900222 show(mHeadsUpStatusBarView);
223 hide(mClockView, View.INVISIBLE);
224 if (mOperatorNameView != null) {
225 hide(mOperatorNameView, View.INVISIBLE);
226 }
Selim Cinekd03518c2018-03-15 12:13:51 -0700227 } else {
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +0900228 show(mClockView);
229 if (mOperatorNameView != null) {
230 show(mOperatorNameView);
231 }
232 hide(mHeadsUpStatusBarView, View.GONE);
Selim Cinekd03518c2018-03-15 12:13:51 -0700233 }
234 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800235 }
236
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +0900237 /**
238 * Hides the view and sets the state to endState when finished.
239 *
240 * @param view The view to hide.
241 * @param endState One of {@link View#INVISIBLE} or {@link View#GONE}.
242 * @see View#setVisibility(int)
243 *
244 */
245 private void hide(View view, int endState) {
246 if (mAnimationsEnabled) {
247 CrossFadeHelper.fadeOut(view, CONTENT_FADE_DURATION /* duration */,
248 0 /* delay */, () -> view.setVisibility(endState));
249 } else {
250 view.setVisibility(endState);
251 }
252 }
253
254 private void show(View view) {
255 if (mAnimationsEnabled) {
256 CrossFadeHelper.fadeIn(view, CONTENT_FADE_DURATION /* duration */,
257 CONTENT_FADE_DELAY /* delay */);
258 } else {
259 view.setVisibility(View.VISIBLE);
260 }
261 }
262
263 @VisibleForTesting
264 void setAnimationsEnabled(boolean enabled) {
265 mAnimationsEnabled = enabled;
266 }
267
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800268 @VisibleForTesting
269 public boolean isShown() {
270 return mShown;
271 }
272
Selim Cinek332c23f2018-03-16 17:37:50 -0700273 /**
274 * Should the headsup status bar view be visible right now? This may be different from isShown,
275 * since the headsUp manager might not have notified us yet of the state change.
276 *
277 * @return if the heads up status bar view should be shown
278 */
279 public boolean shouldBeVisible() {
280 return !mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp();
281 }
282
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800283 @Override
284 public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
285 updateTopEntry();
286 updateHeader(headsUp.getEntry());
287 }
288
289 public void setExpandedHeight(float expandedHeight, float appearFraction) {
290 boolean changedHeight = expandedHeight != mExpandedHeight;
291 mExpandedHeight = expandedHeight;
292 mExpandFraction = appearFraction;
293 boolean isExpanded = expandedHeight > 0;
294 if (changedHeight) {
295 updateHeadsUpHeaders();
296 }
297 if (isExpanded != mIsExpanded) {
298 mIsExpanded = isExpanded;
299 updateTopEntry();
300 }
301 }
302
303 /**
304 * Set a headsUp to be tracked, meaning that it is currently being pulled down after being
305 * in a pinned state on the top. The expand animation is different in that case and we need
306 * to update the header constantly afterwards.
307 *
308 * @param trackedChild the tracked headsUp or null if it's not tracking anymore.
309 */
310 public void setTrackingHeadsUp(ExpandableNotificationRow trackedChild) {
311 ExpandableNotificationRow previousTracked = mTrackedChild;
312 mTrackedChild = trackedChild;
313 if (previousTracked != null) {
314 updateHeader(previousTracked.getEntry());
315 }
316 }
317
318 private void updateHeadsUpHeaders() {
319 mHeadsUpManager.getAllEntries().forEach(entry -> {
320 updateHeader(entry);
321 });
322 }
323
Selim Cinekf0c79e12018-05-14 17:17:31 -0700324 public void updateHeader(NotificationData.Entry entry) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800325 ExpandableNotificationRow row = entry.row;
326 float headerVisibleAmount = 1.0f;
Selim Cinekf0c79e12018-05-14 17:17:31 -0700327 if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800328 headerVisibleAmount = mExpandFraction;
329 }
330 row.setHeaderVisibleAmount(headerVisibleAmount);
331 }
332
333 @Override
334 public void onDarkChanged(Rect area, float darkIntensity, int tint) {
335 mHeadsUpStatusBarView.onDarkChanged(area, darkIntensity, tint);
336 }
337
338 public void setPublicMode(boolean publicMode) {
339 mHeadsUpStatusBarView.setPublicMode(publicMode);
340 updateTopEntry();
341 }
342}