blob: da41366dce85889a136498c4924432bce5509ea1 [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;
felkachang08579552018-05-24 15:38:04 +080058 @VisibleForTesting
59 float mExpandedHeight;
60 @VisibleForTesting
61 boolean mIsExpanded;
62 @VisibleForTesting
63 float mExpandFraction;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080064 private ExpandableNotificationRow mTrackedChild;
65 private boolean mShown;
Selim Cinek60ffea62018-03-22 13:16:44 -070066 private final View.OnLayoutChangeListener mStackScrollLayoutChangeListener =
67 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
68 -> updatePanelTranslation();
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +090069 private boolean mAnimationsEnabled = true;
felkachang3d00f352018-05-22 12:53:50 +080070 Point mPoint;
Selim Cinekaa9db1f2018-02-27 17:35:47 -080071
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +090072
Selim Cinekaa9db1f2018-02-27 17:35:47 -080073 public HeadsUpAppearanceController(
74 NotificationIconAreaController notificationIconAreaController,
75 HeadsUpManagerPhone headsUpManager,
76 View statusbarView) {
77 this(notificationIconAreaController, headsUpManager,
78 statusbarView.findViewById(R.id.heads_up_status_bar_view),
79 statusbarView.findViewById(R.id.notification_stack_scroller),
80 statusbarView.findViewById(R.id.notification_panel),
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +090081 statusbarView.findViewById(R.id.clock),
82 statusbarView.findViewById(R.id.operator_name_frame));
Selim Cinekaa9db1f2018-02-27 17:35:47 -080083 }
84
85 @VisibleForTesting
86 public HeadsUpAppearanceController(
87 NotificationIconAreaController notificationIconAreaController,
88 HeadsUpManagerPhone headsUpManager,
89 HeadsUpStatusBarView headsUpStatusBarView,
90 NotificationStackScrollLayout stackScroller,
91 NotificationPanelView panelView,
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +090092 View clockView,
93 View operatorNameView) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -080094 mNotificationIconAreaController = notificationIconAreaController;
95 mHeadsUpManager = headsUpManager;
96 mHeadsUpManager.addListener(this);
97 mHeadsUpStatusBarView = headsUpStatusBarView;
Selim Cinek332c23f2018-03-16 17:37:50 -070098 headsUpStatusBarView.setOnDrawingRectChangedListener(
99 () -> updateIsolatedIconLocation(true /* requireUpdate */));
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800100 mStackScroller = stackScroller;
Selim Cinek60ffea62018-03-22 13:16:44 -0700101 mPanelView = panelView;
102 panelView.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
103 panelView.addVerticalTranslationListener(mUpdatePanelTranslation);
Selim Cinek332c23f2018-03-16 17:37:50 -0700104 panelView.setHeadsUpAppearanceController(this);
Selim Cinek60ffea62018-03-22 13:16:44 -0700105 mStackScroller.addOnExpandedHeightListener(mSetExpandedHeight);
106 mStackScroller.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
Selim Cinekf0c79e12018-05-14 17:17:31 -0700107 mStackScroller.setHeadsUpAppearanceController(this);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800108 mClockView = clockView;
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +0900109 mOperatorNameView = operatorNameView;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800110 mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
111 mDarkIconDispatcher.addDarkReceiver(this);
felkachang08579552018-05-24 15:38:04 +0800112
113 mHeadsUpStatusBarView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
114 @Override
115 public void onLayoutChange(View v, int left, int top, int right, int bottom,
116 int oldLeft, int oldTop, int oldRight, int oldBottom) {
117 if (shouldBeVisible()) {
118 updateTopEntry();
119
120 // trigger scroller to notify the latest panel translation
121 mStackScroller.requestLayout();
122 }
123 mHeadsUpStatusBarView.removeOnLayoutChangeListener(this);
124 }
125 });
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800126 }
127
Selim Cinek60ffea62018-03-22 13:16:44 -0700128
129 public void destroy() {
130 mHeadsUpManager.removeListener(this);
131 mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
132 mPanelView.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
133 mPanelView.removeVerticalTranslationListener(mUpdatePanelTranslation);
134 mPanelView.setHeadsUpAppearanceController(null);
135 mStackScroller.removeOnExpandedHeightListener(mSetExpandedHeight);
136 mStackScroller.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
137 mDarkIconDispatcher.removeDarkReceiver(this);
138 }
139
Selim Cinek332c23f2018-03-16 17:37:50 -0700140 private void updateIsolatedIconLocation(boolean requireStateUpdate) {
141 mNotificationIconAreaController.setIsolatedIconLocation(
142 mHeadsUpStatusBarView.getIconDrawingRect(), requireStateUpdate);
143 }
144
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800145 @Override
146 public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
147 updateTopEntry();
148 updateHeader(headsUp.getEntry());
149 }
150
felkachang3d00f352018-05-22 12:53:50 +0800151 /** To count the distance from the window right boundary to scroller right boundary. The
152 * distance formula is the following:
153 * Y = screenSize - (SystemWindow's width + Scroller.getRight())
154 * There are four modes MUST to be considered in Cut Out of RTL.
155 * No Cut Out:
156 * Scroller + NB
157 * NB + Scroller
158 * => SystemWindow = NavigationBar's width
159 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
160 * Corner Cut Out or Tall Cut Out:
161 * cut out + Scroller + NB
162 * NB + Scroller + cut out
163 * => SystemWindow = NavigationBar's width
164 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
165 * Double Cut Out:
166 * cut out left + Scroller + (NB + cut out right)
167 * SystemWindow = NavigationBar's width + cut out right width
168 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
169 * (cut out left + NB) + Scroller + cut out right
170 * SystemWindow = NavigationBar's width + cut out left width
171 * => Y = screenSize - (SystemWindow's width + Scroller.getRight())
172 * @return the translation X value for RTL. In theory, it should be negative. i.e. -Y
173 */
174 private int getRtlTranslation() {
felkachang3d00f352018-05-22 12:53:50 +0800175 if (mPoint == null) {
176 mPoint = new Point();
177 }
178
179 int realDisplaySize = 0;
180 if (mStackScroller.getDisplay() != null) {
181 mStackScroller.getDisplay().getRealSize(mPoint);
182 realDisplaySize = mPoint.x;
183 }
184
185 WindowInsets windowInset = mStackScroller.getRootWindowInsets();
felkachang7749c9a2018-06-11 15:56:15 +0800186 DisplayCutout cutout = (windowInset != null) ? windowInset.getDisplayCutout() : null;
187 int sysWinLeft = (windowInset != null) ? windowInset.getStableInsetLeft() : 0;
188 int sysWinRight = (windowInset != null) ? windowInset.getStableInsetRight() : 0;
189 int cutoutLeft = (cutout != null) ? cutout.getSafeInsetLeft() : 0;
190 int cutoutRight = (cutout != null) ? cutout.getSafeInsetRight() : 0;
191 int leftInset = Math.max(sysWinLeft, cutoutLeft);
192 int rightInset = Math.max(sysWinRight, cutoutRight);
193
194 return leftInset + mStackScroller.getRight() + rightInset - realDisplaySize;
felkachang3d00f352018-05-22 12:53:50 +0800195 }
196
Selim Cinek332c23f2018-03-16 17:37:50 -0700197 public void updatePanelTranslation() {
felkachang3d00f352018-05-22 12:53:50 +0800198 float newTranslation;
199 if (mStackScroller.isLayoutRtl()) {
200 newTranslation = getRtlTranslation();
201 } else {
202 newTranslation = mStackScroller.getLeft();
203 }
204 newTranslation += mStackScroller.getTranslationX();
felkachange8a35362018-05-18 20:11:38 +0800205 mHeadsUpStatusBarView.setPanelTranslation(newTranslation);
Selim Cinek332c23f2018-03-16 17:37:50 -0700206 }
207
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800208 private void updateTopEntry() {
209 NotificationData.Entry newEntry = null;
210 if (!mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp()) {
211 newEntry = mHeadsUpManager.getTopEntry();
212 }
213 NotificationData.Entry previousEntry = mHeadsUpStatusBarView.getShowingEntry();
214 mHeadsUpStatusBarView.setEntry(newEntry);
215 if (newEntry != previousEntry) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700216 boolean animateIsolation = false;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800217 if (newEntry == null) {
218 // no heads up anymore, lets start the disappear animation
219
220 setShown(false);
Selim Cinekd03518c2018-03-15 12:13:51 -0700221 animateIsolation = !mIsExpanded;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800222 } else if (previousEntry == null) {
223 // We now have a headsUp and didn't have one before. Let's start the disappear
224 // animation
225 setShown(true);
Selim Cinek332c23f2018-03-16 17:37:50 -0700226 animateIsolation = !mIsExpanded;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800227 }
Selim Cinek332c23f2018-03-16 17:37:50 -0700228 updateIsolatedIconLocation(false /* requireUpdate */);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800229 mNotificationIconAreaController.showIconIsolated(newEntry == null ? null
Selim Cinek332c23f2018-03-16 17:37:50 -0700230 : newEntry.icon, animateIsolation);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800231 }
232 }
233
234 private void setShown(boolean isShown) {
Selim Cinekd03518c2018-03-15 12:13:51 -0700235 if (mShown != isShown) {
236 mShown = isShown;
237 if (isShown) {
238 mHeadsUpStatusBarView.setVisibility(View.VISIBLE);
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +0900239 show(mHeadsUpStatusBarView);
240 hide(mClockView, View.INVISIBLE);
241 if (mOperatorNameView != null) {
242 hide(mOperatorNameView, View.INVISIBLE);
243 }
Selim Cinekd03518c2018-03-15 12:13:51 -0700244 } else {
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +0900245 show(mClockView);
246 if (mOperatorNameView != null) {
247 show(mOperatorNameView);
248 }
249 hide(mHeadsUpStatusBarView, View.GONE);
Selim Cinekd03518c2018-03-15 12:13:51 -0700250 }
251 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800252 }
253
Tetsutoki Shiozawaf1e0f7a2018-09-05 13:17:01 +0900254 /**
255 * Hides the view and sets the state to endState when finished.
256 *
257 * @param view The view to hide.
258 * @param endState One of {@link View#INVISIBLE} or {@link View#GONE}.
259 * @see View#setVisibility(int)
260 *
261 */
262 private void hide(View view, int endState) {
263 if (mAnimationsEnabled) {
264 CrossFadeHelper.fadeOut(view, CONTENT_FADE_DURATION /* duration */,
265 0 /* delay */, () -> view.setVisibility(endState));
266 } else {
267 view.setVisibility(endState);
268 }
269 }
270
271 private void show(View view) {
272 if (mAnimationsEnabled) {
273 CrossFadeHelper.fadeIn(view, CONTENT_FADE_DURATION /* duration */,
274 CONTENT_FADE_DELAY /* delay */);
275 } else {
276 view.setVisibility(View.VISIBLE);
277 }
278 }
279
280 @VisibleForTesting
281 void setAnimationsEnabled(boolean enabled) {
282 mAnimationsEnabled = enabled;
283 }
284
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800285 @VisibleForTesting
286 public boolean isShown() {
287 return mShown;
288 }
289
Selim Cinek332c23f2018-03-16 17:37:50 -0700290 /**
291 * Should the headsup status bar view be visible right now? This may be different from isShown,
292 * since the headsUp manager might not have notified us yet of the state change.
293 *
294 * @return if the heads up status bar view should be shown
295 */
296 public boolean shouldBeVisible() {
297 return !mIsExpanded && mHeadsUpManager.hasPinnedHeadsUp();
298 }
299
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800300 @Override
301 public void onHeadsUpUnPinned(ExpandableNotificationRow headsUp) {
302 updateTopEntry();
303 updateHeader(headsUp.getEntry());
304 }
305
306 public void setExpandedHeight(float expandedHeight, float appearFraction) {
307 boolean changedHeight = expandedHeight != mExpandedHeight;
308 mExpandedHeight = expandedHeight;
309 mExpandFraction = appearFraction;
310 boolean isExpanded = expandedHeight > 0;
311 if (changedHeight) {
312 updateHeadsUpHeaders();
313 }
314 if (isExpanded != mIsExpanded) {
315 mIsExpanded = isExpanded;
316 updateTopEntry();
317 }
318 }
319
320 /**
321 * Set a headsUp to be tracked, meaning that it is currently being pulled down after being
322 * in a pinned state on the top. The expand animation is different in that case and we need
323 * to update the header constantly afterwards.
324 *
325 * @param trackedChild the tracked headsUp or null if it's not tracking anymore.
326 */
327 public void setTrackingHeadsUp(ExpandableNotificationRow trackedChild) {
328 ExpandableNotificationRow previousTracked = mTrackedChild;
329 mTrackedChild = trackedChild;
330 if (previousTracked != null) {
331 updateHeader(previousTracked.getEntry());
332 }
333 }
334
335 private void updateHeadsUpHeaders() {
336 mHeadsUpManager.getAllEntries().forEach(entry -> {
337 updateHeader(entry);
338 });
339 }
340
Selim Cinekf0c79e12018-05-14 17:17:31 -0700341 public void updateHeader(NotificationData.Entry entry) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800342 ExpandableNotificationRow row = entry.row;
343 float headerVisibleAmount = 1.0f;
Selim Cinekf0c79e12018-05-14 17:17:31 -0700344 if (row.isPinned() || row.isHeadsUpAnimatingAway() || row == mTrackedChild) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800345 headerVisibleAmount = mExpandFraction;
346 }
347 row.setHeaderVisibleAmount(headerVisibleAmount);
348 }
349
350 @Override
351 public void onDarkChanged(Rect area, float darkIntensity, int tint) {
352 mHeadsUpStatusBarView.onDarkChanged(area, darkIntensity, tint);
353 }
354
355 public void setPublicMode(boolean publicMode) {
356 mHeadsUpStatusBarView.setPublicMode(publicMode);
357 updateTopEntry();
358 }
felkachang08579552018-05-24 15:38:04 +0800359
360 void readFrom(HeadsUpAppearanceController oldController) {
361 if (oldController != null) {
362 mTrackedChild = oldController.mTrackedChild;
363 mExpandedHeight = oldController.mExpandedHeight;
364 mIsExpanded = oldController.mIsExpanded;
365 mExpandFraction = oldController.mExpandFraction;
366 }
367 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800368}